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+_~\-\.%=&amp;]*)?)?(?:#[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  }