github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/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/mattermost/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  func (sa StringArray) Equals(input StringArray) bool {
    40  
    41  	if len(sa) != len(input) {
    42  		return false
    43  	}
    44  
    45  	for index := range sa {
    46  
    47  		if sa[index] != input[index] {
    48  			return false
    49  		}
    50  	}
    51  
    52  	return true
    53  }
    54  
    55  var translateFunc goi18n.TranslateFunc = nil
    56  
    57  func AppErrorInit(t goi18n.TranslateFunc) {
    58  	translateFunc = t
    59  }
    60  
    61  type AppError struct {
    62  	Id            string `json:"id"`
    63  	Message       string `json:"message"`               // Message to be display to the end user without debugging information
    64  	DetailedError string `json:"detailed_error"`        // Internal error string to help the developer
    65  	RequestId     string `json:"request_id,omitempty"`  // The RequestId that's also set in the header
    66  	StatusCode    int    `json:"status_code,omitempty"` // The http status code
    67  	Where         string `json:"-"`                     // The function where it happened in the form of Struct.Func
    68  	IsOAuth       bool   `json:"is_oauth,omitempty"`    // Whether the error is OAuth specific
    69  	params        map[string]interface{}
    70  }
    71  
    72  func (er *AppError) Error() string {
    73  	return er.Where + ": " + er.Message + ", " + er.DetailedError
    74  }
    75  
    76  func (er *AppError) Translate(T goi18n.TranslateFunc) {
    77  	if T == nil {
    78  		er.Message = er.Id
    79  		return
    80  	}
    81  
    82  	if er.params == nil {
    83  		er.Message = T(er.Id)
    84  	} else {
    85  		er.Message = T(er.Id, er.params)
    86  	}
    87  }
    88  
    89  func (er *AppError) SystemMessage(T goi18n.TranslateFunc) string {
    90  	if er.params == nil {
    91  		return T(er.Id)
    92  	} else {
    93  		return T(er.Id, er.params)
    94  	}
    95  }
    96  
    97  func (er *AppError) ToJson() string {
    98  	b, _ := json.Marshal(er)
    99  	return string(b)
   100  }
   101  
   102  // AppErrorFromJson will decode the input and return an AppError
   103  func AppErrorFromJson(data io.Reader) *AppError {
   104  	str := ""
   105  	bytes, rerr := ioutil.ReadAll(data)
   106  	if rerr != nil {
   107  		str = rerr.Error()
   108  	} else {
   109  		str = string(bytes)
   110  	}
   111  
   112  	decoder := json.NewDecoder(strings.NewReader(str))
   113  	var er AppError
   114  	err := decoder.Decode(&er)
   115  	if err == nil {
   116  		return &er
   117  	} else {
   118  		return NewAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str, http.StatusInternalServerError)
   119  	}
   120  }
   121  
   122  func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError {
   123  	ap := &AppError{}
   124  	ap.Id = id
   125  	ap.params = params
   126  	ap.Message = id
   127  	ap.Where = where
   128  	ap.DetailedError = details
   129  	ap.StatusCode = status
   130  	ap.IsOAuth = false
   131  	ap.Translate(translateFunc)
   132  	return ap
   133  }
   134  
   135  var encoding = base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769")
   136  
   137  // NewId is a globally unique identifier.  It is a [A-Z0-9] string 26
   138  // characters long.  It is a UUID version 4 Guid that is zbased32 encoded
   139  // with the padding stripped off.
   140  func NewId() string {
   141  	var b bytes.Buffer
   142  	encoder := base32.NewEncoder(encoding, &b)
   143  	encoder.Write(uuid.NewRandom())
   144  	encoder.Close()
   145  	b.Truncate(26) // removes the '==' padding
   146  	return b.String()
   147  }
   148  
   149  // NewRandomTeamName is a NewId that will be a valid team name.
   150  func NewRandomTeamName() string {
   151  	teamName := NewId()
   152  	for IsReservedTeamName(teamName) {
   153  		teamName = NewId()
   154  	}
   155  	return teamName
   156  }
   157  
   158  // NewRandomString returns a random string of the given length.
   159  // The resulting entropy will be (5 * length) bits.
   160  func NewRandomString(length int) string {
   161  	data := make([]byte, 1+(length*5/8))
   162  	rand.Read(data)
   163  	return encoding.EncodeToString(data)[:length]
   164  }
   165  
   166  // NewRandomBase32String returns a base32 encoded string of a random slice
   167  // of bytes of the given size. The resulting entropy will be (8 * size) bits.
   168  func NewRandomBase32String(size int) string {
   169  	data := make([]byte, size)
   170  	rand.Read(data)
   171  	return base32.StdEncoding.EncodeToString(data)
   172  }
   173  
   174  // GetMillis is a convenience method to get milliseconds since epoch.
   175  func GetMillis() int64 {
   176  	return time.Now().UnixNano() / int64(time.Millisecond)
   177  }
   178  
   179  // GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time.
   180  func GetMillisForTime(thisTime time.Time) int64 {
   181  	return thisTime.UnixNano() / int64(time.Millisecond)
   182  }
   183  
   184  // PadDateStringZeros is a convenience method to pad 2 digit date parts with zeros to meet ISO 8601 format
   185  func PadDateStringZeros(dateString string) string {
   186  	parts := strings.Split(dateString, "-")
   187  	for index, part := range parts {
   188  		if len(part) == 1 {
   189  			parts[index] = "0" + part
   190  		}
   191  	}
   192  	dateString = strings.Join(parts[:], "-")
   193  	return dateString
   194  }
   195  
   196  // GetStartOfDayMillis is a convenience method to get milliseconds since epoch for provided date's start of day
   197  func GetStartOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 {
   198  	localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset)
   199  	resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 0, 0, 0, 0, localSearchTimeZone)
   200  	return GetMillisForTime(resultTime)
   201  }
   202  
   203  // GetEndOfDayMillis is a convenience method to get milliseconds since epoch for provided date's end of day
   204  func GetEndOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 {
   205  	localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset)
   206  	resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 23, 59, 59, 999999999, localSearchTimeZone)
   207  	return GetMillisForTime(resultTime)
   208  }
   209  
   210  func CopyStringMap(originalMap map[string]string) map[string]string {
   211  	copyMap := make(map[string]string)
   212  	for k, v := range originalMap {
   213  		copyMap[k] = v
   214  	}
   215  	return copyMap
   216  }
   217  
   218  // MapToJson converts a map to a json string
   219  func MapToJson(objmap map[string]string) string {
   220  	b, _ := json.Marshal(objmap)
   221  	return string(b)
   222  }
   223  
   224  // MapBoolToJson converts a map to a json string
   225  func MapBoolToJson(objmap map[string]bool) string {
   226  	b, _ := json.Marshal(objmap)
   227  	return string(b)
   228  }
   229  
   230  // MapFromJson will decode the key/value pair map
   231  func MapFromJson(data io.Reader) map[string]string {
   232  	decoder := json.NewDecoder(data)
   233  
   234  	var objmap map[string]string
   235  	if err := decoder.Decode(&objmap); err != nil {
   236  		return make(map[string]string)
   237  	} else {
   238  		return objmap
   239  	}
   240  }
   241  
   242  // MapFromJson will decode the key/value pair map
   243  func MapBoolFromJson(data io.Reader) map[string]bool {
   244  	decoder := json.NewDecoder(data)
   245  
   246  	var objmap map[string]bool
   247  	if err := decoder.Decode(&objmap); err != nil {
   248  		return make(map[string]bool)
   249  	} else {
   250  		return objmap
   251  	}
   252  }
   253  
   254  func ArrayToJson(objmap []string) string {
   255  	b, _ := json.Marshal(objmap)
   256  	return string(b)
   257  }
   258  
   259  func ArrayFromJson(data io.Reader) []string {
   260  	decoder := json.NewDecoder(data)
   261  
   262  	var objmap []string
   263  	if err := decoder.Decode(&objmap); err != nil {
   264  		return make([]string, 0)
   265  	} else {
   266  		return objmap
   267  	}
   268  }
   269  
   270  func ArrayFromInterface(data interface{}) []string {
   271  	stringArray := []string{}
   272  
   273  	dataArray, ok := data.([]interface{})
   274  	if !ok {
   275  		return stringArray
   276  	}
   277  
   278  	for _, v := range dataArray {
   279  		if str, ok := v.(string); ok {
   280  			stringArray = append(stringArray, str)
   281  		}
   282  	}
   283  
   284  	return stringArray
   285  }
   286  
   287  func StringInterfaceToJson(objmap map[string]interface{}) string {
   288  	b, _ := json.Marshal(objmap)
   289  	return string(b)
   290  }
   291  
   292  func StringInterfaceFromJson(data io.Reader) map[string]interface{} {
   293  	decoder := json.NewDecoder(data)
   294  
   295  	var objmap map[string]interface{}
   296  	if err := decoder.Decode(&objmap); err != nil {
   297  		return make(map[string]interface{})
   298  	} else {
   299  		return objmap
   300  	}
   301  }
   302  
   303  func StringToJson(s string) string {
   304  	b, _ := json.Marshal(s)
   305  	return string(b)
   306  }
   307  
   308  func StringFromJson(data io.Reader) string {
   309  	decoder := json.NewDecoder(data)
   310  
   311  	var s string
   312  	if err := decoder.Decode(&s); err != nil {
   313  		return ""
   314  	} else {
   315  		return s
   316  	}
   317  }
   318  
   319  func GetServerIpAddress(iface string) string {
   320  	var addrs []net.Addr
   321  	if len(iface) == 0 {
   322  		var err error
   323  		addrs, err = net.InterfaceAddrs()
   324  		if err != nil {
   325  			return ""
   326  		}
   327  	} else {
   328  		interfaces, err := net.Interfaces()
   329  		if err != nil {
   330  			return ""
   331  		}
   332  		for _, i := range interfaces {
   333  			if i.Name == iface {
   334  				addrs, err = i.Addrs()
   335  				if err != nil {
   336  					return ""
   337  				}
   338  				break
   339  			}
   340  		}
   341  	}
   342  
   343  	for _, addr := range addrs {
   344  
   345  		if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() && !ip.IP.IsLinkLocalMulticast() {
   346  			if ip.IP.To4() != nil {
   347  				return ip.IP.String()
   348  			}
   349  		}
   350  	}
   351  
   352  	return ""
   353  }
   354  
   355  func IsLower(s string) bool {
   356  	return strings.ToLower(s) == s
   357  }
   358  
   359  func IsValidEmail(email string) bool {
   360  	if !IsLower(email) {
   361  		return false
   362  	}
   363  
   364  	if addr, err := mail.ParseAddress(email); err != nil {
   365  		return false
   366  	} else if addr.Name != "" {
   367  		// mail.ParseAddress accepts input of the form "Billy Bob <billy@example.com>" which we don't allow
   368  		return false
   369  	}
   370  
   371  	return true
   372  }
   373  
   374  var reservedName = []string{
   375  	"admin",
   376  	"api",
   377  	"channel",
   378  	"claim",
   379  	"error",
   380  	"help",
   381  	"landing",
   382  	"login",
   383  	"mfa",
   384  	"oauth",
   385  	"plug",
   386  	"plugins",
   387  	"post",
   388  	"signup",
   389  }
   390  
   391  func IsValidChannelIdentifier(s string) bool {
   392  
   393  	if !IsValidAlphaNumHyphenUnderscore(s, true) {
   394  		return false
   395  	}
   396  
   397  	if len(s) < CHANNEL_NAME_MIN_LENGTH {
   398  		return false
   399  	}
   400  
   401  	return true
   402  }
   403  
   404  func IsValidAlphaNum(s string) bool {
   405  	validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`)
   406  
   407  	return validAlphaNum.MatchString(s)
   408  }
   409  
   410  func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool {
   411  	if withFormat {
   412  		validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`)
   413  		return validAlphaNumHyphenUnderscore.MatchString(s)
   414  	}
   415  
   416  	validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
   417  	return validSimpleAlphaNumHyphenUnderscore.MatchString(s)
   418  }
   419  
   420  func Etag(parts ...interface{}) string {
   421  
   422  	etag := CurrentVersion
   423  
   424  	for _, part := range parts {
   425  		etag += fmt.Sprintf(".%v", part)
   426  	}
   427  
   428  	return etag
   429  }
   430  
   431  var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`)
   432  var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`)
   433  var hashtagStart = regexp.MustCompile(`^#{2,}`)
   434  var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`)
   435  
   436  func ParseHashtags(text string) (string, string) {
   437  	words := strings.Fields(text)
   438  
   439  	hashtagString := ""
   440  	plainString := ""
   441  	for _, word := range words {
   442  		// trim off surrounding punctuation
   443  		word = puncStart.ReplaceAllString(word, "")
   444  		word = puncEnd.ReplaceAllString(word, "")
   445  
   446  		// and remove extra pound #s
   447  		word = hashtagStart.ReplaceAllString(word, "#")
   448  
   449  		if validHashtag.MatchString(word) {
   450  			hashtagString += " " + word
   451  		} else {
   452  			plainString += " " + word
   453  		}
   454  	}
   455  
   456  	if len(hashtagString) > 1000 {
   457  		hashtagString = hashtagString[:999]
   458  		lastSpace := strings.LastIndex(hashtagString, " ")
   459  		if lastSpace > -1 {
   460  			hashtagString = hashtagString[:lastSpace]
   461  		} else {
   462  			hashtagString = ""
   463  		}
   464  	}
   465  
   466  	return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString)
   467  }
   468  
   469  func IsFileExtImage(ext string) bool {
   470  	ext = strings.ToLower(ext)
   471  	for _, imgExt := range IMAGE_EXTENSIONS {
   472  		if ext == imgExt {
   473  			return true
   474  		}
   475  	}
   476  	return false
   477  }
   478  
   479  func GetImageMimeType(ext string) string {
   480  	ext = strings.ToLower(ext)
   481  	if len(IMAGE_MIME_TYPES[ext]) == 0 {
   482  		return "image"
   483  	} else {
   484  		return IMAGE_MIME_TYPES[ext]
   485  	}
   486  }
   487  
   488  func ClearMentionTags(post string) string {
   489  	post = strings.Replace(post, "<mention>", "", -1)
   490  	post = strings.Replace(post, "</mention>", "", -1)
   491  	return post
   492  }
   493  
   494  func IsValidHttpUrl(rawUrl string) bool {
   495  	if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 {
   496  		return false
   497  	}
   498  
   499  	if _, err := url.ParseRequestURI(rawUrl); err != nil {
   500  		return false
   501  	}
   502  
   503  	return true
   504  }
   505  
   506  func IsValidTurnOrStunServer(rawUri string) bool {
   507  	if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 {
   508  		return false
   509  	}
   510  
   511  	if _, err := url.ParseRequestURI(rawUri); err != nil {
   512  		return false
   513  	}
   514  
   515  	return true
   516  }
   517  
   518  func IsSafeLink(link *string) bool {
   519  	if link != nil {
   520  		if IsValidHttpUrl(*link) {
   521  			return true
   522  		} else if strings.HasPrefix(*link, "/") {
   523  			return true
   524  		} else {
   525  			return false
   526  		}
   527  	}
   528  
   529  	return true
   530  }
   531  
   532  func IsValidWebsocketUrl(rawUrl string) bool {
   533  	if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 {
   534  		return false
   535  	}
   536  
   537  	if _, err := url.ParseRequestURI(rawUrl); err != nil {
   538  		return false
   539  	}
   540  
   541  	return true
   542  }
   543  
   544  func IsValidTrueOrFalseString(value string) bool {
   545  	return value == "true" || value == "false"
   546  }
   547  
   548  func IsValidNumberString(value string) bool {
   549  	if _, err := strconv.Atoi(value); err != nil {
   550  		return false
   551  	}
   552  
   553  	return true
   554  }
   555  
   556  func IsValidId(value string) bool {
   557  	if len(value) != 26 {
   558  		return false
   559  	}
   560  
   561  	for _, r := range value {
   562  		if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
   563  			return false
   564  		}
   565  	}
   566  
   567  	return true
   568  }
   569  
   570  // Copied from https://golang.org/src/net/dnsclient.go#L119
   571  func IsDomainName(s string) bool {
   572  	// See RFC 1035, RFC 3696.
   573  	// Presentation format has dots before every label except the first, and the
   574  	// terminal empty label is optional here because we assume fully-qualified
   575  	// (absolute) input. We must therefore reserve space for the first and last
   576  	// labels' length octets in wire format, where they are necessary and the
   577  	// maximum total length is 255.
   578  	// So our _effective_ maximum is 253, but 254 is not rejected if the last
   579  	// character is a dot.
   580  	l := len(s)
   581  	if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
   582  		return false
   583  	}
   584  
   585  	last := byte('.')
   586  	ok := false // Ok once we've seen a letter.
   587  	partlen := 0
   588  	for i := 0; i < len(s); i++ {
   589  		c := s[i]
   590  		switch {
   591  		default:
   592  			return false
   593  		case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_':
   594  			ok = true
   595  			partlen++
   596  		case '0' <= c && c <= '9':
   597  			// fine
   598  			partlen++
   599  		case c == '-':
   600  			// Byte before dash cannot be dot.
   601  			if last == '.' {
   602  				return false
   603  			}
   604  			partlen++
   605  		case c == '.':
   606  			// Byte before dot cannot be dot, dash.
   607  			if last == '.' || last == '-' {
   608  				return false
   609  			}
   610  			if partlen > 63 || partlen == 0 {
   611  				return false
   612  			}
   613  			partlen = 0
   614  		}
   615  		last = c
   616  	}
   617  	if last == '-' || partlen > 63 {
   618  		return false
   619  	}
   620  
   621  	return ok
   622  }
   623  
   624  func RemoveDuplicateStrings(in []string) []string {
   625  	out := []string{}
   626  	seen := make(map[string]bool, len(in))
   627  
   628  	for _, item := range in {
   629  		if !seen[item] {
   630  			out = append(out, item)
   631  
   632  			seen[item] = true
   633  		}
   634  	}
   635  
   636  	return out
   637  }
   638  
   639  func GetPreferredTimezone(timezone StringMap) string {
   640  	if timezone["useAutomaticTimezone"] == "true" {
   641  		return timezone["automaticTimezone"]
   642  	}
   643  
   644  	return timezone["manualTimezone"]
   645  }
   646  
   647  // IsSamlFile checks if filename is a SAML file.
   648  func IsSamlFile(saml *SamlSettings, filename string) bool {
   649  	return filename == *saml.PublicCertificateFile || filename == *saml.PrivateKeyFile || filename == *saml.IdpCertificateFile
   650  }
   651  
   652  func AsStringBoolMap(list []string) map[string]bool {
   653  	listMap := map[string]bool{}
   654  	for _, p := range list {
   655  		listMap[p] = true
   656  	}
   657  	return listMap
   658  }
   659  
   660  // SanitizeUnicode will remove undesirable Unicode characters from a string.
   661  func SanitizeUnicode(s string) string {
   662  	return strings.Map(filterBlocklist, s)
   663  }
   664  
   665  // filterBlocklist returns `r` if it is not in the blocklist, otherwise drop (-1).
   666  // Blocklist is taken from https://www.w3.org/TR/unicode-xml/#Charlist
   667  func filterBlocklist(r rune) rune {
   668  	const drop = -1
   669  	switch r {
   670  	case '\u0340', '\u0341': // clones of grave and acute; deprecated in Unicode
   671  		return drop
   672  	case '\u17A3', '\u17D3': // obsolete characters for Khmer; deprecated in Unicode
   673  		return drop
   674  	case '\u2028', '\u2029': // line and paragraph separator
   675  		return drop
   676  	case '\u202A', '\u202B', '\u202C', '\u202D', '\u202E': // BIDI embedding controls
   677  		return drop
   678  	case '\u206A', '\u206B': // activate/inhibit symmetric swapping; deprecated in Unicode
   679  		return drop
   680  	case '\u206C', '\u206D': // activate/inhibit Arabic form shaping; deprecated in Unicode
   681  		return drop
   682  	case '\u206E', '\u206F': // activate/inhibit national digit shapes; deprecated in Unicode
   683  		return drop
   684  	case '\uFFF9', '\uFFFA', '\uFFFB': // interlinear annotation characters
   685  		return drop
   686  	case '\uFEFF': // byte order mark
   687  		return drop
   688  	case '\uFFFC': // object replacement character
   689  		return drop
   690  	}
   691  
   692  	// Scoping for musical notation
   693  	if r >= 0x0001D173 && r <= 0x0001D17A {
   694  		return drop
   695  	}
   696  
   697  	// Language tag code points
   698  	if r >= 0x000E0000 && r <= 0x000E007F {
   699  		return drop
   700  	}
   701  
   702  	return r
   703  }