github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+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 283 if !IsLower(email) { 284 return false 285 } 286 287 if _, err := mail.ParseAddress(email); err == nil { 288 return true 289 } 290 291 return false 292 } 293 294 var reservedName = []string{ 295 "signup", 296 "login", 297 "admin", 298 "channel", 299 "post", 300 "api", 301 "oauth", 302 } 303 304 func IsValidChannelIdentifier(s string) bool { 305 306 if !IsValidAlphaNumHyphenUnderscore(s, true) { 307 return false 308 } 309 310 if len(s) < CHANNEL_NAME_MIN_LENGTH { 311 return false 312 } 313 314 return true 315 } 316 317 func IsValidAlphaNum(s string) bool { 318 validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`) 319 320 return validAlphaNum.MatchString(s) 321 } 322 323 func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool { 324 if withFormat { 325 validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`) 326 return validAlphaNumHyphenUnderscore.MatchString(s) 327 } 328 329 validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) 330 return validSimpleAlphaNumHyphenUnderscore.MatchString(s) 331 } 332 333 func Etag(parts ...interface{}) string { 334 335 etag := CurrentVersion 336 337 for _, part := range parts { 338 etag += fmt.Sprintf(".%v", part) 339 } 340 341 return etag 342 } 343 344 var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`) 345 var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) 346 var hashtagStart = regexp.MustCompile(`^#{2,}`) 347 var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) 348 349 func ParseHashtags(text string) (string, string) { 350 words := strings.Fields(text) 351 352 hashtagString := "" 353 plainString := "" 354 for _, word := range words { 355 // trim off surrounding punctuation 356 word = puncStart.ReplaceAllString(word, "") 357 word = puncEnd.ReplaceAllString(word, "") 358 359 // and remove extra pound #s 360 word = hashtagStart.ReplaceAllString(word, "#") 361 362 if validHashtag.MatchString(word) { 363 hashtagString += " " + word 364 } else { 365 plainString += " " + word 366 } 367 } 368 369 if len(hashtagString) > 1000 { 370 hashtagString = hashtagString[:999] 371 lastSpace := strings.LastIndex(hashtagString, " ") 372 if lastSpace > -1 { 373 hashtagString = hashtagString[:lastSpace] 374 } else { 375 hashtagString = "" 376 } 377 } 378 379 return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString) 380 } 381 382 func IsFileExtImage(ext string) bool { 383 ext = strings.ToLower(ext) 384 for _, imgExt := range IMAGE_EXTENSIONS { 385 if ext == imgExt { 386 return true 387 } 388 } 389 return false 390 } 391 392 func GetImageMimeType(ext string) string { 393 ext = strings.ToLower(ext) 394 if len(IMAGE_MIME_TYPES[ext]) == 0 { 395 return "image" 396 } else { 397 return IMAGE_MIME_TYPES[ext] 398 } 399 } 400 401 func ClearMentionTags(post string) string { 402 post = strings.Replace(post, "<mention>", "", -1) 403 post = strings.Replace(post, "</mention>", "", -1) 404 return post 405 } 406 407 func IsValidHttpUrl(rawUrl string) bool { 408 if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 { 409 return false 410 } 411 412 if _, err := url.ParseRequestURI(rawUrl); err != nil { 413 return false 414 } 415 416 return true 417 } 418 419 func IsValidTurnOrStunServer(rawUri string) bool { 420 if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 { 421 return false 422 } 423 424 if _, err := url.ParseRequestURI(rawUri); err != nil { 425 return false 426 } 427 428 return true 429 } 430 431 func IsSafeLink(link *string) bool { 432 if link != nil { 433 if IsValidHttpUrl(*link) { 434 return true 435 } else if strings.HasPrefix(*link, "/") { 436 return true 437 } else { 438 return false 439 } 440 } 441 442 return true 443 } 444 445 func IsValidWebsocketUrl(rawUrl string) bool { 446 if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 { 447 return false 448 } 449 450 if _, err := url.ParseRequestURI(rawUrl); err != nil { 451 return false 452 } 453 454 return true 455 } 456 457 func IsValidTrueOrFalseString(value string) bool { 458 return value == "true" || value == "false" 459 } 460 461 func IsValidNumberString(value string) bool { 462 if _, err := strconv.Atoi(value); err != nil { 463 return false 464 } 465 466 return true 467 } 468 469 func IsValidId(value string) bool { 470 if len(value) != 26 { 471 return false 472 } 473 474 for _, r := range value { 475 if !unicode.IsLetter(r) && !unicode.IsNumber(r) { 476 return false 477 } 478 } 479 480 return true 481 } 482 483 // checkNowhereNil checks that the given interface value is not nil, and if a struct, that all of 484 // its public fields are also nowhere nil 485 func checkNowhereNil(t *testing.T, name string, value interface{}) bool { 486 if value == nil { 487 return false 488 } 489 490 v := reflect.ValueOf(value) 491 switch v.Type().Kind() { 492 case reflect.Ptr: 493 if v.IsNil() { 494 t.Logf("%s was nil", name) 495 return false 496 } 497 498 return checkNowhereNil(t, fmt.Sprintf("(*%s)", name), v.Elem().Interface()) 499 500 case reflect.Map: 501 if v.IsNil() { 502 t.Logf("%s was nil", name) 503 return false 504 } 505 506 // Don't check map values 507 return true 508 509 case reflect.Struct: 510 nowhereNil := true 511 for i := 0; i < v.NumField(); i++ { 512 f := v.Field(i) 513 // Ignore unexported fields 514 if v.Type().Field(i).PkgPath != "" { 515 continue 516 } 517 518 nowhereNil = nowhereNil && checkNowhereNil(t, fmt.Sprintf("%s.%s", name, v.Type().Field(i).Name), f.Interface()) 519 } 520 521 return nowhereNil 522 523 case reflect.Array: 524 fallthrough 525 case reflect.Chan: 526 fallthrough 527 case reflect.Func: 528 fallthrough 529 case reflect.Interface: 530 fallthrough 531 case reflect.UnsafePointer: 532 t.Logf("unhandled field %s, type: %s", name, v.Type().Kind()) 533 return false 534 535 default: 536 return true 537 } 538 }