/model/utils.go
Go | 538 lines | 423 code | 98 blank | 17 comment | 104 complexity | be9b3e02bece094e566d5a9b8f256019 MD5 | raw file
- // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
- // See License.txt for license information.
- package model
- import (
- "bytes"
- "crypto/rand"
- "encoding/base32"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "net/mail"
- "net/url"
- "reflect"
- "regexp"
- "strconv"
- "strings"
- "testing"
- "time"
- "unicode"
- goi18n "github.com/nicksnyder/go-i18n/i18n"
- "github.com/pborman/uuid"
- )
- const (
- LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"
- UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- NUMBERS = "0123456789"
- SYMBOLS = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~"
- )
- type StringInterface map[string]interface{}
- type StringMap map[string]string
- type StringArray []string
- var translateFunc goi18n.TranslateFunc = nil
- func AppErrorInit(t goi18n.TranslateFunc) {
- translateFunc = t
- }
- type AppError struct {
- Id string `json:"id"`
- Message string `json:"message"` // Message to be display to the end user without debugging information
- DetailedError string `json:"detailed_error"` // Internal error string to help the developer
- RequestId string `json:"request_id,omitempty"` // The RequestId that's also set in the header
- StatusCode int `json:"status_code,omitempty"` // The http status code
- Where string `json:"-"` // The function where it happened in the form of Struct.Func
- IsOAuth bool `json:"is_oauth,omitempty"` // Whether the error is OAuth specific
- params map[string]interface{}
- }
- func (er *AppError) Error() string {
- return er.Where + ": " + er.Message + ", " + er.DetailedError
- }
- func (er *AppError) Translate(T goi18n.TranslateFunc) {
- if T == nil {
- er.Message = er.Id
- return
- }
- if er.params == nil {
- er.Message = T(er.Id)
- } else {
- er.Message = T(er.Id, er.params)
- }
- }
- func (er *AppError) SystemMessage(T goi18n.TranslateFunc) string {
- if er.params == nil {
- return T(er.Id)
- } else {
- return T(er.Id, er.params)
- }
- }
- func (er *AppError) ToJson() string {
- b, _ := json.Marshal(er)
- return string(b)
- }
- // AppErrorFromJson will decode the input and return an AppError
- func AppErrorFromJson(data io.Reader) *AppError {
- str := ""
- bytes, rerr := ioutil.ReadAll(data)
- if rerr != nil {
- str = rerr.Error()
- } else {
- str = string(bytes)
- }
- decoder := json.NewDecoder(strings.NewReader(str))
- var er AppError
- err := decoder.Decode(&er)
- if err == nil {
- return &er
- } else {
- return NewAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str, http.StatusInternalServerError)
- }
- }
- func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError {
- ap := &AppError{}
- ap.Id = id
- ap.params = params
- ap.Message = id
- ap.Where = where
- ap.DetailedError = details
- ap.StatusCode = status
- ap.IsOAuth = false
- ap.Translate(translateFunc)
- return ap
- }
- var encoding = base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769")
- // NewId is a globally unique identifier. It is a [A-Z0-9] string 26
- // characters long. It is a UUID version 4 Guid that is zbased32 encoded
- // with the padding stripped off.
- func NewId() string {
- var b bytes.Buffer
- encoder := base32.NewEncoder(encoding, &b)
- encoder.Write(uuid.NewRandom())
- encoder.Close()
- b.Truncate(26) // removes the '==' padding
- return b.String()
- }
- func NewRandomString(length int) string {
- var b bytes.Buffer
- str := make([]byte, length+8)
- rand.Read(str)
- encoder := base32.NewEncoder(encoding, &b)
- encoder.Write(str)
- encoder.Close()
- b.Truncate(length) // removes the '==' padding
- return b.String()
- }
- // GetMillis is a convience method to get milliseconds since epoch.
- func GetMillis() int64 {
- return time.Now().UnixNano() / int64(time.Millisecond)
- }
- func CopyStringMap(originalMap map[string]string) map[string]string {
- copyMap := make(map[string]string)
- for k, v := range originalMap {
- copyMap[k] = v
- }
- return copyMap
- }
- // MapToJson converts a map to a json string
- func MapToJson(objmap map[string]string) string {
- b, _ := json.Marshal(objmap)
- return string(b)
- }
- // MapToJson converts a map to a json string
- func MapBoolToJson(objmap map[string]bool) string {
- b, _ := json.Marshal(objmap)
- return string(b)
- }
- // MapFromJson will decode the key/value pair map
- func MapFromJson(data io.Reader) map[string]string {
- decoder := json.NewDecoder(data)
- var objmap map[string]string
- if err := decoder.Decode(&objmap); err != nil {
- return make(map[string]string)
- } else {
- return objmap
- }
- }
- // MapFromJson will decode the key/value pair map
- func MapBoolFromJson(data io.Reader) map[string]bool {
- decoder := json.NewDecoder(data)
- var objmap map[string]bool
- if err := decoder.Decode(&objmap); err != nil {
- return make(map[string]bool)
- } else {
- return objmap
- }
- }
- func ArrayToJson(objmap []string) string {
- b, _ := json.Marshal(objmap)
- return string(b)
- }
- func ArrayFromJson(data io.Reader) []string {
- decoder := json.NewDecoder(data)
- var objmap []string
- if err := decoder.Decode(&objmap); err != nil {
- return make([]string, 0)
- } else {
- return objmap
- }
- }
- func ArrayFromInterface(data interface{}) []string {
- stringArray := []string{}
- dataArray, ok := data.([]interface{})
- if !ok {
- return stringArray
- }
- for _, v := range dataArray {
- if str, ok := v.(string); ok {
- stringArray = append(stringArray, str)
- }
- }
- return stringArray
- }
- func StringInterfaceToJson(objmap map[string]interface{}) string {
- b, _ := json.Marshal(objmap)
- return string(b)
- }
- func StringInterfaceFromJson(data io.Reader) map[string]interface{} {
- decoder := json.NewDecoder(data)
- var objmap map[string]interface{}
- if err := decoder.Decode(&objmap); err != nil {
- return make(map[string]interface{})
- } else {
- return objmap
- }
- }
- func StringToJson(s string) string {
- b, _ := json.Marshal(s)
- return string(b)
- }
- func StringFromJson(data io.Reader) string {
- decoder := json.NewDecoder(data)
- var s string
- if err := decoder.Decode(&s); err != nil {
- return ""
- } else {
- return s
- }
- }
- func GetServerIpAddress() string {
- if addrs, err := net.InterfaceAddrs(); err != nil {
- return ""
- } else {
- for _, addr := range addrs {
- if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() {
- if ip.IP.To4() != nil {
- return ip.IP.String()
- }
- }
- }
- }
- return ""
- }
- func IsLower(s string) bool {
- return strings.ToLower(s) == s
- }
- func IsValidEmail(email string) bool {
- if !IsLower(email) {
- return false
- }
- if _, err := mail.ParseAddress(email); err == nil {
- return true
- }
- return false
- }
- var reservedName = []string{
- "signup",
- "login",
- "admin",
- "channel",
- "post",
- "api",
- "oauth",
- }
- func IsValidChannelIdentifier(s string) bool {
- if !IsValidAlphaNumHyphenUnderscore(s, true) {
- return false
- }
- if len(s) < CHANNEL_NAME_MIN_LENGTH {
- return false
- }
- return true
- }
- func IsValidAlphaNum(s string) bool {
- validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`)
- return validAlphaNum.MatchString(s)
- }
- func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool {
- if withFormat {
- validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`)
- return validAlphaNumHyphenUnderscore.MatchString(s)
- }
- validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
- return validSimpleAlphaNumHyphenUnderscore.MatchString(s)
- }
- func Etag(parts ...interface{}) string {
- etag := CurrentVersion
- for _, part := range parts {
- etag += fmt.Sprintf(".%v", part)
- }
- return etag
- }
- var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`)
- var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`)
- var hashtagStart = regexp.MustCompile(`^#{2,}`)
- var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`)
- func ParseHashtags(text string) (string, string) {
- words := strings.Fields(text)
- hashtagString := ""
- plainString := ""
- for _, word := range words {
- // trim off surrounding punctuation
- word = puncStart.ReplaceAllString(word, "")
- word = puncEnd.ReplaceAllString(word, "")
- // and remove extra pound #s
- word = hashtagStart.ReplaceAllString(word, "#")
- if validHashtag.MatchString(word) {
- hashtagString += " " + word
- } else {
- plainString += " " + word
- }
- }
- if len(hashtagString) > 1000 {
- hashtagString = hashtagString[:999]
- lastSpace := strings.LastIndex(hashtagString, " ")
- if lastSpace > -1 {
- hashtagString = hashtagString[:lastSpace]
- } else {
- hashtagString = ""
- }
- }
- return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString)
- }
- func IsFileExtImage(ext string) bool {
- ext = strings.ToLower(ext)
- for _, imgExt := range IMAGE_EXTENSIONS {
- if ext == imgExt {
- return true
- }
- }
- return false
- }
- func GetImageMimeType(ext string) string {
- ext = strings.ToLower(ext)
- if len(IMAGE_MIME_TYPES[ext]) == 0 {
- return "image"
- } else {
- return IMAGE_MIME_TYPES[ext]
- }
- }
- func ClearMentionTags(post string) string {
- post = strings.Replace(post, "<mention>", "", -1)
- post = strings.Replace(post, "</mention>", "", -1)
- return post
- }
- func IsValidHttpUrl(rawUrl string) bool {
- if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 {
- return false
- }
- if _, err := url.ParseRequestURI(rawUrl); err != nil {
- return false
- }
- return true
- }
- func IsValidTurnOrStunServer(rawUri string) bool {
- if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 {
- return false
- }
- if _, err := url.ParseRequestURI(rawUri); err != nil {
- return false
- }
- return true
- }
- func IsSafeLink(link *string) bool {
- if link != nil {
- if IsValidHttpUrl(*link) {
- return true
- } else if strings.HasPrefix(*link, "/") {
- return true
- } else {
- return false
- }
- }
- return true
- }
- func IsValidWebsocketUrl(rawUrl string) bool {
- if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 {
- return false
- }
- if _, err := url.ParseRequestURI(rawUrl); err != nil {
- return false
- }
- return true
- }
- func IsValidTrueOrFalseString(value string) bool {
- return value == "true" || value == "false"
- }
- func IsValidNumberString(value string) bool {
- if _, err := strconv.Atoi(value); err != nil {
- return false
- }
- return true
- }
- func IsValidId(value string) bool {
- if len(value) != 26 {
- return false
- }
- for _, r := range value {
- if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
- return false
- }
- }
- return true
- }
- // checkNowhereNil checks that the given interface value is not nil, and if a struct, that all of
- // its public fields are also nowhere nil
- func checkNowhereNil(t *testing.T, name string, value interface{}) bool {
- if value == nil {
- return false
- }
- v := reflect.ValueOf(value)
- switch v.Type().Kind() {
- case reflect.Ptr:
- if v.IsNil() {
- t.Logf("%s was nil", name)
- return false
- }
- return checkNowhereNil(t, fmt.Sprintf("(*%s)", name), v.Elem().Interface())
- case reflect.Map:
- if v.IsNil() {
- t.Logf("%s was nil", name)
- return false
- }
- // Don't check map values
- return true
- case reflect.Struct:
- nowhereNil := true
- for i := 0; i < v.NumField(); i++ {
- f := v.Field(i)
- // Ignore unexported fields
- if v.Type().Field(i).PkgPath != "" {
- continue
- }
- nowhereNil = nowhereNil && checkNowhereNil(t, fmt.Sprintf("%s.%s", name, v.Type().Field(i).Name), f.Interface())
- }
- return nowhereNil
- case reflect.Array:
- fallthrough
- case reflect.Chan:
- fallthrough
- case reflect.Func:
- fallthrough
- case reflect.Interface:
- fallthrough
- case reflect.UnsafePointer:
- t.Logf("unhandled field %s, type: %s", name, v.Type().Kind())
- return false
- default:
- return true
- }
- }