github.com/wgh-/mattermost-server@v4.8.0-rc2+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 // MapToJson converts a map to a json string 150 func MapToJson(objmap map[string]string) string { 151 b, _ := json.Marshal(objmap) 152 return string(b) 153 } 154 155 // MapToJson converts a map to a json string 156 func MapBoolToJson(objmap map[string]bool) string { 157 b, _ := json.Marshal(objmap) 158 return string(b) 159 } 160 161 // MapFromJson will decode the key/value pair map 162 func MapFromJson(data io.Reader) map[string]string { 163 decoder := json.NewDecoder(data) 164 165 var objmap map[string]string 166 if err := decoder.Decode(&objmap); err != nil { 167 return make(map[string]string) 168 } else { 169 return objmap 170 } 171 } 172 173 // MapFromJson will decode the key/value pair map 174 func MapBoolFromJson(data io.Reader) map[string]bool { 175 decoder := json.NewDecoder(data) 176 177 var objmap map[string]bool 178 if err := decoder.Decode(&objmap); err != nil { 179 return make(map[string]bool) 180 } else { 181 return objmap 182 } 183 } 184 185 func ArrayToJson(objmap []string) string { 186 b, _ := json.Marshal(objmap) 187 return string(b) 188 } 189 190 func ArrayFromJson(data io.Reader) []string { 191 decoder := json.NewDecoder(data) 192 193 var objmap []string 194 if err := decoder.Decode(&objmap); err != nil { 195 return make([]string, 0) 196 } else { 197 return objmap 198 } 199 } 200 201 func ArrayFromInterface(data interface{}) []string { 202 stringArray := []string{} 203 204 dataArray, ok := data.([]interface{}) 205 if !ok { 206 return stringArray 207 } 208 209 for _, v := range dataArray { 210 if str, ok := v.(string); ok { 211 stringArray = append(stringArray, str) 212 } 213 } 214 215 return stringArray 216 } 217 218 func StringInterfaceToJson(objmap map[string]interface{}) string { 219 b, _ := json.Marshal(objmap) 220 return string(b) 221 } 222 223 func StringInterfaceFromJson(data io.Reader) map[string]interface{} { 224 decoder := json.NewDecoder(data) 225 226 var objmap map[string]interface{} 227 if err := decoder.Decode(&objmap); err != nil { 228 return make(map[string]interface{}) 229 } else { 230 return objmap 231 } 232 } 233 234 func StringToJson(s string) string { 235 b, _ := json.Marshal(s) 236 return string(b) 237 } 238 239 func StringFromJson(data io.Reader) string { 240 decoder := json.NewDecoder(data) 241 242 var s string 243 if err := decoder.Decode(&s); err != nil { 244 return "" 245 } else { 246 return s 247 } 248 } 249 250 func GetServerIpAddress() string { 251 if addrs, err := net.InterfaceAddrs(); err != nil { 252 return "" 253 } else { 254 for _, addr := range addrs { 255 256 if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() { 257 if ip.IP.To4() != nil { 258 return ip.IP.String() 259 } 260 } 261 } 262 } 263 264 return "" 265 } 266 267 func IsLower(s string) bool { 268 return strings.ToLower(s) == s 269 } 270 271 func IsValidEmail(email string) bool { 272 273 if !IsLower(email) { 274 return false 275 } 276 277 if _, err := mail.ParseAddress(email); err == nil { 278 return true 279 } 280 281 return false 282 } 283 284 var reservedName = []string{ 285 "signup", 286 "login", 287 "admin", 288 "channel", 289 "post", 290 "api", 291 "oauth", 292 } 293 294 func IsValidChannelIdentifier(s string) bool { 295 296 if !IsValidAlphaNumHyphenUnderscore(s, true) { 297 return false 298 } 299 300 if len(s) < CHANNEL_NAME_MIN_LENGTH { 301 return false 302 } 303 304 return true 305 } 306 307 func IsValidAlphaNum(s string) bool { 308 validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`) 309 310 return validAlphaNum.MatchString(s) 311 } 312 313 func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool { 314 if withFormat { 315 validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`) 316 return validAlphaNumHyphenUnderscore.MatchString(s) 317 } 318 319 validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`) 320 return validSimpleAlphaNumHyphenUnderscore.MatchString(s) 321 } 322 323 func Etag(parts ...interface{}) string { 324 325 etag := CurrentVersion 326 327 for _, part := range parts { 328 etag += fmt.Sprintf(".%v", part) 329 } 330 331 return etag 332 } 333 334 var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`) 335 var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`) 336 var hashtagStart = regexp.MustCompile(`^#{2,}`) 337 var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`) 338 339 func ParseHashtags(text string) (string, string) { 340 words := strings.Fields(text) 341 342 hashtagString := "" 343 plainString := "" 344 for _, word := range words { 345 // trim off surrounding punctuation 346 word = puncStart.ReplaceAllString(word, "") 347 word = puncEnd.ReplaceAllString(word, "") 348 349 // and remove extra pound #s 350 word = hashtagStart.ReplaceAllString(word, "#") 351 352 if validHashtag.MatchString(word) { 353 hashtagString += " " + word 354 } else { 355 plainString += " " + word 356 } 357 } 358 359 if len(hashtagString) > 1000 { 360 hashtagString = hashtagString[:999] 361 lastSpace := strings.LastIndex(hashtagString, " ") 362 if lastSpace > -1 { 363 hashtagString = hashtagString[:lastSpace] 364 } else { 365 hashtagString = "" 366 } 367 } 368 369 return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString) 370 } 371 372 func IsFileExtImage(ext string) bool { 373 ext = strings.ToLower(ext) 374 for _, imgExt := range IMAGE_EXTENSIONS { 375 if ext == imgExt { 376 return true 377 } 378 } 379 return false 380 } 381 382 func GetImageMimeType(ext string) string { 383 ext = strings.ToLower(ext) 384 if len(IMAGE_MIME_TYPES[ext]) == 0 { 385 return "image" 386 } else { 387 return IMAGE_MIME_TYPES[ext] 388 } 389 } 390 391 func ClearMentionTags(post string) string { 392 post = strings.Replace(post, "<mention>", "", -1) 393 post = strings.Replace(post, "</mention>", "", -1) 394 return post 395 } 396 397 var UrlRegex = regexp.MustCompile(`^((?:[a-z]+:\/\/)?(?:(?:[a-z0-9\-]+\.)+(?:[a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(?:\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(?:\?[a-z0-9+_~\-\.%=&]*)?)?(?:#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(?:\s+|$)$`) 398 var PartialUrlRegex = regexp.MustCompile(`/([A-Za-z0-9]{26})/([A-Za-z0-9]{26})/((?:[A-Za-z0-9]{26})?.+(?:\.[A-Za-z0-9]{3,})?)`) 399 400 func IsValidHttpUrl(rawUrl string) bool { 401 if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 { 402 return false 403 } 404 405 if _, err := url.ParseRequestURI(rawUrl); err != nil { 406 return false 407 } 408 409 return true 410 } 411 412 func IsValidHttpsUrl(rawUrl string) bool { 413 if strings.Index(rawUrl, "https://") != 0 { 414 return false 415 } 416 417 if _, err := url.ParseRequestURI(rawUrl); err != nil { 418 return false 419 } 420 421 return true 422 } 423 424 func IsValidTurnOrStunServer(rawUri string) bool { 425 if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 { 426 return false 427 } 428 429 if _, err := url.ParseRequestURI(rawUri); err != nil { 430 return false 431 } 432 433 return true 434 } 435 436 func IsSafeLink(link *string) bool { 437 if link != nil { 438 if IsValidHttpUrl(*link) { 439 return true 440 } else if strings.HasPrefix(*link, "/") { 441 return true 442 } else { 443 return false 444 } 445 } 446 447 return true 448 } 449 450 func IsValidWebsocketUrl(rawUrl string) bool { 451 if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 { 452 return false 453 } 454 455 if _, err := url.ParseRequestURI(rawUrl); err != nil { 456 return false 457 } 458 459 return true 460 } 461 462 func IsValidTrueOrFalseString(value string) bool { 463 return value == "true" || value == "false" 464 } 465 466 func IsValidNumberString(value string) bool { 467 if _, err := strconv.Atoi(value); err != nil { 468 return false 469 } 470 471 return true 472 } 473 474 func IsValidId(value string) bool { 475 if len(value) != 26 { 476 return false 477 } 478 479 for _, r := range value { 480 if !unicode.IsLetter(r) && !unicode.IsNumber(r) { 481 return false 482 } 483 } 484 485 return true 486 }