github.com/hahmadia/mattermost-server@v5.11.1+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  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  func NewRandomString(length int) string {
   150  	var b bytes.Buffer
   151  	str := make([]byte, length+8)
   152  	rand.Read(str)
   153  	encoder := base32.NewEncoder(encoding, &b)
   154  	encoder.Write(str)
   155  	encoder.Close()
   156  	b.Truncate(length) // removes the '==' padding
   157  	return b.String()
   158  }
   159  
   160  // GetMillis is a convenience method to get milliseconds since epoch.
   161  func GetMillis() int64 {
   162  	return time.Now().UnixNano() / int64(time.Millisecond)
   163  }
   164  
   165  // GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time.
   166  func GetMillisForTime(thisTime time.Time) int64 {
   167  	return thisTime.UnixNano() / int64(time.Millisecond)
   168  }
   169  
   170  // PadDateStringZeros is a convenience method to pad 2 digit date parts with zeros to meet ISO 8601 format
   171  func PadDateStringZeros(dateString string) string {
   172  	parts := strings.Split(dateString, "-")
   173  	for index, part := range parts {
   174  		if len(part) == 1 {
   175  			parts[index] = "0" + part
   176  		}
   177  	}
   178  	dateString = strings.Join(parts[:], "-")
   179  	return dateString
   180  }
   181  
   182  // GetStartOfDayMillis is a convenience method to get milliseconds since epoch for provided date's start of day
   183  func GetStartOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 {
   184  	localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset)
   185  	resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 0, 0, 0, 0, localSearchTimeZone)
   186  	return GetMillisForTime(resultTime)
   187  }
   188  
   189  // GetEndOfDayMillis is a convenience method to get milliseconds since epoch for provided date's end of day
   190  func GetEndOfDayMillis(thisTime time.Time, timeZoneOffset int) int64 {
   191  	localSearchTimeZone := time.FixedZone("Local Search Time Zone", timeZoneOffset)
   192  	resultTime := time.Date(thisTime.Year(), thisTime.Month(), thisTime.Day(), 23, 59, 59, 999999999, localSearchTimeZone)
   193  	return GetMillisForTime(resultTime)
   194  }
   195  
   196  func CopyStringMap(originalMap map[string]string) map[string]string {
   197  	copyMap := make(map[string]string)
   198  	for k, v := range originalMap {
   199  		copyMap[k] = v
   200  	}
   201  	return copyMap
   202  }
   203  
   204  // MapToJson converts a map to a json string
   205  func MapToJson(objmap map[string]string) string {
   206  	b, _ := json.Marshal(objmap)
   207  	return string(b)
   208  }
   209  
   210  // MapToJson converts a map to a json string
   211  func MapBoolToJson(objmap map[string]bool) string {
   212  	b, _ := json.Marshal(objmap)
   213  	return string(b)
   214  }
   215  
   216  // MapFromJson will decode the key/value pair map
   217  func MapFromJson(data io.Reader) map[string]string {
   218  	decoder := json.NewDecoder(data)
   219  
   220  	var objmap map[string]string
   221  	if err := decoder.Decode(&objmap); err != nil {
   222  		return make(map[string]string)
   223  	} else {
   224  		return objmap
   225  	}
   226  }
   227  
   228  // MapFromJson will decode the key/value pair map
   229  func MapBoolFromJson(data io.Reader) map[string]bool {
   230  	decoder := json.NewDecoder(data)
   231  
   232  	var objmap map[string]bool
   233  	if err := decoder.Decode(&objmap); err != nil {
   234  		return make(map[string]bool)
   235  	} else {
   236  		return objmap
   237  	}
   238  }
   239  
   240  func ArrayToJson(objmap []string) string {
   241  	b, _ := json.Marshal(objmap)
   242  	return string(b)
   243  }
   244  
   245  func ArrayFromJson(data io.Reader) []string {
   246  	decoder := json.NewDecoder(data)
   247  
   248  	var objmap []string
   249  	if err := decoder.Decode(&objmap); err != nil {
   250  		return make([]string, 0)
   251  	} else {
   252  		return objmap
   253  	}
   254  }
   255  
   256  func ArrayFromInterface(data interface{}) []string {
   257  	stringArray := []string{}
   258  
   259  	dataArray, ok := data.([]interface{})
   260  	if !ok {
   261  		return stringArray
   262  	}
   263  
   264  	for _, v := range dataArray {
   265  		if str, ok := v.(string); ok {
   266  			stringArray = append(stringArray, str)
   267  		}
   268  	}
   269  
   270  	return stringArray
   271  }
   272  
   273  func StringInterfaceToJson(objmap map[string]interface{}) string {
   274  	b, _ := json.Marshal(objmap)
   275  	return string(b)
   276  }
   277  
   278  func StringInterfaceFromJson(data io.Reader) map[string]interface{} {
   279  	decoder := json.NewDecoder(data)
   280  
   281  	var objmap map[string]interface{}
   282  	if err := decoder.Decode(&objmap); err != nil {
   283  		return make(map[string]interface{})
   284  	} else {
   285  		return objmap
   286  	}
   287  }
   288  
   289  func StringToJson(s string) string {
   290  	b, _ := json.Marshal(s)
   291  	return string(b)
   292  }
   293  
   294  func StringFromJson(data io.Reader) string {
   295  	decoder := json.NewDecoder(data)
   296  
   297  	var s string
   298  	if err := decoder.Decode(&s); err != nil {
   299  		return ""
   300  	} else {
   301  		return s
   302  	}
   303  }
   304  
   305  func GetServerIpAddress() string {
   306  	if addrs, err := net.InterfaceAddrs(); err != nil {
   307  		return ""
   308  	} else {
   309  		for _, addr := range addrs {
   310  
   311  			if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() && !ip.IP.IsLinkLocalMulticast() {
   312  				if ip.IP.To4() != nil {
   313  					return ip.IP.String()
   314  				}
   315  			}
   316  		}
   317  	}
   318  
   319  	return ""
   320  }
   321  
   322  func IsLower(s string) bool {
   323  	return strings.ToLower(s) == s
   324  }
   325  
   326  func IsValidEmail(email string) bool {
   327  	if !IsLower(email) {
   328  		return false
   329  	}
   330  
   331  	if addr, err := mail.ParseAddress(email); err != nil {
   332  		return false
   333  	} else if addr.Name != "" {
   334  		// mail.ParseAddress accepts input of the form "Billy Bob <billy@example.com>" which we don't allow
   335  		return false
   336  	}
   337  
   338  	return true
   339  }
   340  
   341  var reservedName = []string{
   342  	"signup",
   343  	"login",
   344  	"admin",
   345  	"channel",
   346  	"post",
   347  	"api",
   348  	"oauth",
   349  }
   350  
   351  func IsValidChannelIdentifier(s string) bool {
   352  
   353  	if !IsValidAlphaNumHyphenUnderscore(s, true) {
   354  		return false
   355  	}
   356  
   357  	if len(s) < CHANNEL_NAME_MIN_LENGTH {
   358  		return false
   359  	}
   360  
   361  	return true
   362  }
   363  
   364  func IsValidAlphaNum(s string) bool {
   365  	validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`)
   366  
   367  	return validAlphaNum.MatchString(s)
   368  }
   369  
   370  func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool {
   371  	if withFormat {
   372  		validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`)
   373  		return validAlphaNumHyphenUnderscore.MatchString(s)
   374  	}
   375  
   376  	validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
   377  	return validSimpleAlphaNumHyphenUnderscore.MatchString(s)
   378  }
   379  
   380  func Etag(parts ...interface{}) string {
   381  
   382  	etag := CurrentVersion
   383  
   384  	for _, part := range parts {
   385  		etag += fmt.Sprintf(".%v", part)
   386  	}
   387  
   388  	return etag
   389  }
   390  
   391  var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`)
   392  var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`)
   393  var hashtagStart = regexp.MustCompile(`^#{2,}`)
   394  var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`)
   395  
   396  func ParseHashtags(text string) (string, string) {
   397  	words := strings.Fields(text)
   398  
   399  	hashtagString := ""
   400  	plainString := ""
   401  	for _, word := range words {
   402  		// trim off surrounding punctuation
   403  		word = puncStart.ReplaceAllString(word, "")
   404  		word = puncEnd.ReplaceAllString(word, "")
   405  
   406  		// and remove extra pound #s
   407  		word = hashtagStart.ReplaceAllString(word, "#")
   408  
   409  		if validHashtag.MatchString(word) {
   410  			hashtagString += " " + word
   411  		} else {
   412  			plainString += " " + word
   413  		}
   414  	}
   415  
   416  	if len(hashtagString) > 1000 {
   417  		hashtagString = hashtagString[:999]
   418  		lastSpace := strings.LastIndex(hashtagString, " ")
   419  		if lastSpace > -1 {
   420  			hashtagString = hashtagString[:lastSpace]
   421  		} else {
   422  			hashtagString = ""
   423  		}
   424  	}
   425  
   426  	return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString)
   427  }
   428  
   429  func IsFileExtImage(ext string) bool {
   430  	ext = strings.ToLower(ext)
   431  	for _, imgExt := range IMAGE_EXTENSIONS {
   432  		if ext == imgExt {
   433  			return true
   434  		}
   435  	}
   436  	return false
   437  }
   438  
   439  func GetImageMimeType(ext string) string {
   440  	ext = strings.ToLower(ext)
   441  	if len(IMAGE_MIME_TYPES[ext]) == 0 {
   442  		return "image"
   443  	} else {
   444  		return IMAGE_MIME_TYPES[ext]
   445  	}
   446  }
   447  
   448  func ClearMentionTags(post string) string {
   449  	post = strings.Replace(post, "<mention>", "", -1)
   450  	post = strings.Replace(post, "</mention>", "", -1)
   451  	return post
   452  }
   453  
   454  func IsValidHttpUrl(rawUrl string) bool {
   455  	if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 {
   456  		return false
   457  	}
   458  
   459  	if _, err := url.ParseRequestURI(rawUrl); err != nil {
   460  		return false
   461  	}
   462  
   463  	return true
   464  }
   465  
   466  func IsValidTurnOrStunServer(rawUri string) bool {
   467  	if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 {
   468  		return false
   469  	}
   470  
   471  	if _, err := url.ParseRequestURI(rawUri); err != nil {
   472  		return false
   473  	}
   474  
   475  	return true
   476  }
   477  
   478  func IsSafeLink(link *string) bool {
   479  	if link != nil {
   480  		if IsValidHttpUrl(*link) {
   481  			return true
   482  		} else if strings.HasPrefix(*link, "/") {
   483  			return true
   484  		} else {
   485  			return false
   486  		}
   487  	}
   488  
   489  	return true
   490  }
   491  
   492  func IsValidWebsocketUrl(rawUrl string) bool {
   493  	if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 {
   494  		return false
   495  	}
   496  
   497  	if _, err := url.ParseRequestURI(rawUrl); err != nil {
   498  		return false
   499  	}
   500  
   501  	return true
   502  }
   503  
   504  func IsValidTrueOrFalseString(value string) bool {
   505  	return value == "true" || value == "false"
   506  }
   507  
   508  func IsValidNumberString(value string) bool {
   509  	if _, err := strconv.Atoi(value); err != nil {
   510  		return false
   511  	}
   512  
   513  	return true
   514  }
   515  
   516  func IsValidId(value string) bool {
   517  	if len(value) != 26 {
   518  		return false
   519  	}
   520  
   521  	for _, r := range value {
   522  		if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
   523  			return false
   524  		}
   525  	}
   526  
   527  	return true
   528  }
   529  
   530  // Copied from https://golang.org/src/net/dnsclient.go#L119
   531  func IsDomainName(s string) bool {
   532  	// See RFC 1035, RFC 3696.
   533  	// Presentation format has dots before every label except the first, and the
   534  	// terminal empty label is optional here because we assume fully-qualified
   535  	// (absolute) input. We must therefore reserve space for the first and last
   536  	// labels' length octets in wire format, where they are necessary and the
   537  	// maximum total length is 255.
   538  	// So our _effective_ maximum is 253, but 254 is not rejected if the last
   539  	// character is a dot.
   540  	l := len(s)
   541  	if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
   542  		return false
   543  	}
   544  
   545  	last := byte('.')
   546  	ok := false // Ok once we've seen a letter.
   547  	partlen := 0
   548  	for i := 0; i < len(s); i++ {
   549  		c := s[i]
   550  		switch {
   551  		default:
   552  			return false
   553  		case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_':
   554  			ok = true
   555  			partlen++
   556  		case '0' <= c && c <= '9':
   557  			// fine
   558  			partlen++
   559  		case c == '-':
   560  			// Byte before dash cannot be dot.
   561  			if last == '.' {
   562  				return false
   563  			}
   564  			partlen++
   565  		case c == '.':
   566  			// Byte before dot cannot be dot, dash.
   567  			if last == '.' || last == '-' {
   568  				return false
   569  			}
   570  			if partlen > 63 || partlen == 0 {
   571  				return false
   572  			}
   573  			partlen = 0
   574  		}
   575  		last = c
   576  	}
   577  	if last == '-' || partlen > 63 {
   578  		return false
   579  	}
   580  
   581  	return ok
   582  }
   583  
   584  func RemoveDuplicateStrings(in []string) []string {
   585  	out := []string{}
   586  	seen := make(map[string]bool, len(in))
   587  
   588  	for _, item := range in {
   589  		if !seen[item] {
   590  			out = append(out, item)
   591  
   592  			seen[item] = true
   593  		}
   594  	}
   595  
   596  	return out
   597  }
   598  
   599  func GetPreferredTimezone(timezone StringMap) string {
   600  	if timezone["useAutomaticTimezone"] == "true" {
   601  		return timezone["automaticTimezone"]
   602  	}
   603  
   604  	return timezone["manualTimezone"]
   605  }