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