github.com/spline-fu/mattermost-server@v4.10.10+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 "reflect" 19 "regexp" 20 "strconv" 21 "strings" 22 "testing" 23 "time" 24 "unicode" 25 26 goi18n "github.com/nicksnyder/go-i18n/i18n" 27 "github.com/pborman/uuid" 28 ) 29 30 const ( 31 LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz" 32 UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" 33 NUMBERS = "0123456789" 34 SYMBOLS = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~" 35 ) 36 37 type StringInterface map[string]interface{} 38 type StringMap map[string]string 39 type StringArray []string 40 41 var translateFunc goi18n.TranslateFunc = nil 42 43 func AppErrorInit(t goi18n.TranslateFunc) { 44 translateFunc = t 45 } 46 47 type AppError struct { 48 Id string `json:"id"` 49 Message string `json:"message"` // Message to be display to the end user without debugging information 50 DetailedError string `json:"detailed_error"` // Internal error string to help the developer 51 RequestId string `json:"request_id,omitempty"` // The RequestId that's also set in the header 52 StatusCode int `json:"status_code,omitempty"` // The http status code 53 Where string `json:"-"` // The function where it happened in the form of Struct.Func 54 IsOAuth bool `json:"is_oauth,omitempty"` // Whether the error is OAuth specific 55 params map[string]interface{} 56 } 57 58 func (er *AppError) Error() string { 59 return er.Where + ": " + er.Message + ", " + er.DetailedError 60 } 61 62 func (er *AppError) Translate(T goi18n.TranslateFunc) { 63 if T == nil { 64 er.Message = er.Id 65 return 66 } 67 68 if er.params == nil { 69 er.Message = T(er.Id) 70 } else { 71 er.Message = T(er.Id, er.params) 72 } 73 } 74 75 func (er *AppError) SystemMessage(T goi18n.TranslateFunc) string { 76 if er.params == nil { 77 return T(er.Id) 78 } else { 79 return T(er.Id, er.params) 80 } 81 } 82 83 func (er *AppError) ToJson() string { 84 b, _ := json.Marshal(er) 85 return string(b) 86 } 87 88 // AppErrorFromJson will decode the input and return an AppError 89 func AppErrorFromJson(data io.Reader) *AppError { 90 str := "" 91 bytes, rerr := ioutil.ReadAll(data) 92 if rerr != nil { 93 str = rerr.Error() 94 } else { 95 str = string(bytes) 96 } 97 98 decoder := json.NewDecoder(strings.NewReader(str)) 99 var er AppError 100 err := decoder.Decode(&er) 101 if err == nil { 102 return &er 103 } else { 104 return NewAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str, http.StatusInternalServerError) 105 } 106 } 107 108 func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError { 109 ap := &AppError{} 110 ap.Id = id 111 ap.params = params 112 ap.Message = id 113 ap.Where = where 114 ap.DetailedError = details 115 ap.StatusCode = status 116 ap.IsOAuth = false 117 ap.Translate(translateFunc) 118 return ap 119 } 120 121 var encoding = base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769") 122 123 // NewId is a globally unique identifier. It is a [A-Z0-9] string 26 124 // characters long. It is a UUID version 4 Guid that is zbased32 encoded 125 // with the padding stripped off. 126 func NewId() string { 127 var b bytes.Buffer 128 encoder := base32.NewEncoder(encoding, &b) 129 encoder.Write(uuid.NewRandom()) 130 encoder.Close() 131 b.Truncate(26) // removes the '==' padding 132 return b.String() 133 } 134 135 func NewRandomString(length int) string { 136 var b bytes.Buffer 137 str := make([]byte, length+8) 138 rand.Read(str) 139 encoder := base32.NewEncoder(encoding, &b) 140 encoder.Write(str) 141 encoder.Close() 142 b.Truncate(length) // removes the '==' padding 143 return b.String() 144 } 145 146 // GetMillis is a convience method to get milliseconds since epoch. 147 func GetMillis() int64 { 148 return time.Now().UnixNano() / int64(time.Millisecond) 149 } 150 151 func CopyStringMap(originalMap map[string]string) map[string]string { 152 copyMap := make(map[string]string) 153 for k, v := range originalMap { 154 copyMap[k] = v 155 } 156 return copyMap 157 } 158 159 // MapToJson converts a map to a json string 160 func MapToJson(objmap map[string]string) string { 161 b, _ := json.Marshal(objmap) 162 return string(b) 163 } 164 165 // MapToJson converts a map to a json string 166 func MapBoolToJson(objmap map[string]bool) string { 167 b, _ := json.Marshal(objmap) 168 return string(b) 169 } 170 171 // MapFromJson will decode the key/value pair map 172 func MapFromJson(data io.Reader) map[string]string { 173 decoder := json.NewDecoder(data) 174 175 var objmap map[string]string 176 if err := decoder.Decode(&objmap); err != nil { 177 return make(map[string]string) 178 } else { 179 return objmap 180 } 181 } 182 183 // MapFromJson will decode the key/value pair map 184 func MapBoolFromJson(data io.Reader) map[string]bool { 185 decoder := json.NewDecoder(data) 186 187 var objmap map[string]bool 188 if err := decoder.Decode(&objmap); err != nil { 189 return make(map[string]bool) 190 } else { 191 return objmap 192 } 193 } 194 195 func ArrayToJson(objmap []string) string { 196 b, _ := json.Marshal(objmap) 197 return string(b) 198 } 199 200 func ArrayFromJson(data io.Reader) []string { 201 decoder := json.NewDecoder(data) 202 203 var objmap []string 204 if err := decoder.Decode(&objmap); err != nil { 205 return make([]string, 0) 206 } else { 207 return objmap 208 } 209 } 210 211 func ArrayFromInterface(data interface{}) []string { 212 stringArray := []string{} 213 214 dataArray, ok := data.([]interface{}) 215 if !ok { 216 return stringArray 217 } 218 219 for _, v := range dataArray { 220 if str, ok := v.(string); ok { 221 stringArray = append(stringArray, str) 222 } 223 } 224 225 return stringArray 226 } 227 228 func StringInterfaceToJson(objmap map[string]interface{}) string { 229 b, _ := json.Marshal(objmap) 230 return string(b) 231 } 232 233 func StringInterfaceFromJson(data io.Reader) map[string]interface{} { 234 decoder := json.NewDecoder(data) 235 236 var objmap map[string]interface{} 237 if err := decoder.Decode(&objmap); err != nil { 238 return make(map[string]interface{}) 239 } else { 240 return objmap 241 } 242 } 243 244 func StringToJson(s string) string { 245 b, _ := json.Marshal(s) 246 return string(b) 247 } 248 249 func StringFromJson(data io.Reader) string { 250 decoder := json.NewDecoder(data) 251 252 var s string 253 if err := decoder.Decode(&s); err != nil { 254 return "" 255 } else { 256 return s 257 } 258 } 259 260 func GetServerIpAddress() string { 261 if addrs, err := net.InterfaceAddrs(); err != nil { 262 return "" 263 } else { 264 for _, addr := range addrs { 265 266 if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() { 267 if ip.IP.To4() != nil { 268 return ip.IP.String() 269 } 270 } 271 } 272 } 273 274 return "" 275 } 276 277 func IsLower(s string) bool { 278 return strings.ToLower(s) == s 279 } 280 281 func IsValidEmail(email string) bool { 282 if !IsLower(email) { 283 return false 284 } 285 286 if addr, err := mail.ParseAddress(email); err != nil { 287 return false 288 } else if addr.Name != "" { 289 // mail.ParseAddress accepts input of the form "Billy Bob <billy@example.com>" which we don't allow 290 return false 291 } 292 293 return true 294 } 295 296 var reservedName = []string{ 297 "signup", 298 "login", 299 "admin", 300 "channel", 301 "post", 302 "api", 303 "oauth", 304 } 305 306 func IsValidChannelIdentifier(s string) bool { 307 308 if !IsValidAlphaNumHyphenUnderscore(s, true) { 309 return false 310 } 311 312 if len(s) < CHANNEL_NAME_MIN_LENGTH { 313 return false 314 } 315 316 return true 317 } 318 319 func IsValidAlphaNum(s string) bool { 320 validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`) 321 322 return validAlphaNum.MatchString(s) 323 } 324 325 func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool { 326 if withFormat { 327 validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`) 328 return validAlphaNumHyphenUnderscore.MatchString(s) 329 } 330 331 validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) 332 return validSimpleAlphaNumHyphenUnderscore.MatchString(s) 333 } 334 335 func Etag(parts ...interface{}) string { 336 337 etag := CurrentVersion 338 339 for _, part := range parts { 340 etag += fmt.Sprintf(".%v", part) 341 } 342 343 return etag 344 } 345 346 var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`) 347 var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) 348 var hashtagStart = regexp.MustCompile(`^#{2,}`) 349 var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) 350 351 func ParseHashtags(text string) (string, string) { 352 words := strings.Fields(text) 353 354 hashtagString := "" 355 plainString := "" 356 for _, word := range words { 357 // trim off surrounding punctuation 358 word = puncStart.ReplaceAllString(word, "") 359 word = puncEnd.ReplaceAllString(word, "") 360 361 // and remove extra pound #s 362 word = hashtagStart.ReplaceAllString(word, "#") 363 364 if validHashtag.MatchString(word) { 365 hashtagString += " " + word 366 } else { 367 plainString += " " + word 368 } 369 } 370 371 if len(hashtagString) > 1000 { 372 hashtagString = hashtagString[:999] 373 lastSpace := strings.LastIndex(hashtagString, " ") 374 if lastSpace > -1 { 375 hashtagString = hashtagString[:lastSpace] 376 } else { 377 hashtagString = "" 378 } 379 } 380 381 return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString) 382 } 383 384 func IsFileExtImage(ext string) bool { 385 ext = strings.ToLower(ext) 386 for _, imgExt := range IMAGE_EXTENSIONS { 387 if ext == imgExt { 388 return true 389 } 390 } 391 return false 392 } 393 394 func GetImageMimeType(ext string) string { 395 ext = strings.ToLower(ext) 396 if len(IMAGE_MIME_TYPES[ext]) == 0 { 397 return "image" 398 } else { 399 return IMAGE_MIME_TYPES[ext] 400 } 401 } 402 403 func ClearMentionTags(post string) string { 404 post = strings.Replace(post, "<mention>", "", -1) 405 post = strings.Replace(post, "</mention>", "", -1) 406 return post 407 } 408 409 func IsValidHttpUrl(rawUrl string) bool { 410 if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 { 411 return false 412 } 413 414 if _, err := url.ParseRequestURI(rawUrl); err != nil { 415 return false 416 } 417 418 return true 419 } 420 421 func IsValidTurnOrStunServer(rawUri string) bool { 422 if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 { 423 return false 424 } 425 426 if _, err := url.ParseRequestURI(rawUri); err != nil { 427 return false 428 } 429 430 return true 431 } 432 433 func IsSafeLink(link *string) bool { 434 if link != nil { 435 if IsValidHttpUrl(*link) { 436 return true 437 } else if strings.HasPrefix(*link, "/") { 438 return true 439 } else { 440 return false 441 } 442 } 443 444 return true 445 } 446 447 func IsValidWebsocketUrl(rawUrl string) bool { 448 if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 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 IsValidTrueOrFalseString(value string) bool { 460 return value == "true" || value == "false" 461 } 462 463 func IsValidNumberString(value string) bool { 464 if _, err := strconv.Atoi(value); err != nil { 465 return false 466 } 467 468 return true 469 } 470 471 func IsValidId(value string) bool { 472 if len(value) != 26 { 473 return false 474 } 475 476 for _, r := range value { 477 if !unicode.IsLetter(r) && !unicode.IsNumber(r) { 478 return false 479 } 480 } 481 482 return true 483 } 484 485 // checkNowhereNil checks that the given interface value is not nil, and if a struct, that all of 486 // its public fields are also nowhere nil 487 func checkNowhereNil(t *testing.T, name string, value interface{}) bool { 488 if value == nil { 489 return false 490 } 491 492 v := reflect.ValueOf(value) 493 switch v.Type().Kind() { 494 case reflect.Ptr: 495 if v.IsNil() { 496 t.Logf("%s was nil", name) 497 return false 498 } 499 500 return checkNowhereNil(t, fmt.Sprintf("(*%s)", name), v.Elem().Interface()) 501 502 case reflect.Map: 503 if v.IsNil() { 504 t.Logf("%s was nil", name) 505 return false 506 } 507 508 // Don't check map values 509 return true 510 511 case reflect.Struct: 512 nowhereNil := true 513 for i := 0; i < v.NumField(); i++ { 514 f := v.Field(i) 515 // Ignore unexported fields 516 if v.Type().Field(i).PkgPath != "" { 517 continue 518 } 519 520 nowhereNil = nowhereNil && checkNowhereNil(t, fmt.Sprintf("%s.%s", name, v.Type().Field(i).Name), f.Interface()) 521 } 522 523 return nowhereNil 524 525 case reflect.Array: 526 fallthrough 527 case reflect.Chan: 528 fallthrough 529 case reflect.Func: 530 fallthrough 531 case reflect.Interface: 532 fallthrough 533 case reflect.UnsafePointer: 534 t.Logf("unhandled field %s, type: %s", name, v.Type().Kind()) 535 return false 536 537 default: 538 return true 539 } 540 }