github.com/spline-fu/mattermost-server@v4.10.10+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  	"reflect"
    19  	"regexp"
    20  	"strconv"
    21  	"strings"
    22  	"testing"
    23  	"time"
    24  	"unicode"
    25  
    26  	goi18n "github.com/nicksnyder/go-i18n/i18n"
    27  	"github.com/pborman/uuid"
    28  )
    29  
    30  const (
    31  	LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"
    32  	UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    33  	NUMBERS           = "0123456789"
    34  	SYMBOLS           = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~"
    35  )
    36  
    37  type StringInterface map[string]interface{}
    38  type StringMap map[string]string
    39  type StringArray []string
    40  
    41  var translateFunc goi18n.TranslateFunc = nil
    42  
    43  func AppErrorInit(t goi18n.TranslateFunc) {
    44  	translateFunc = t
    45  }
    46  
    47  type AppError struct {
    48  	Id            string `json:"id"`
    49  	Message       string `json:"message"`               // Message to be display to the end user without debugging information
    50  	DetailedError string `json:"detailed_error"`        // Internal error string to help the developer
    51  	RequestId     string `json:"request_id,omitempty"`  // The RequestId that's also set in the header
    52  	StatusCode    int    `json:"status_code,omitempty"` // The http status code
    53  	Where         string `json:"-"`                     // The function where it happened in the form of Struct.Func
    54  	IsOAuth       bool   `json:"is_oauth,omitempty"`    // Whether the error is OAuth specific
    55  	params        map[string]interface{}
    56  }
    57  
    58  func (er *AppError) Error() string {
    59  	return er.Where + ": " + er.Message + ", " + er.DetailedError
    60  }
    61  
    62  func (er *AppError) Translate(T goi18n.TranslateFunc) {
    63  	if T == nil {
    64  		er.Message = er.Id
    65  		return
    66  	}
    67  
    68  	if er.params == nil {
    69  		er.Message = T(er.Id)
    70  	} else {
    71  		er.Message = T(er.Id, er.params)
    72  	}
    73  }
    74  
    75  func (er *AppError) SystemMessage(T goi18n.TranslateFunc) string {
    76  	if er.params == nil {
    77  		return T(er.Id)
    78  	} else {
    79  		return T(er.Id, er.params)
    80  	}
    81  }
    82  
    83  func (er *AppError) ToJson() string {
    84  	b, _ := json.Marshal(er)
    85  	return string(b)
    86  }
    87  
    88  // AppErrorFromJson will decode the input and return an AppError
    89  func AppErrorFromJson(data io.Reader) *AppError {
    90  	str := ""
    91  	bytes, rerr := ioutil.ReadAll(data)
    92  	if rerr != nil {
    93  		str = rerr.Error()
    94  	} else {
    95  		str = string(bytes)
    96  	}
    97  
    98  	decoder := json.NewDecoder(strings.NewReader(str))
    99  	var er AppError
   100  	err := decoder.Decode(&er)
   101  	if err == nil {
   102  		return &er
   103  	} else {
   104  		return NewAppError("AppErrorFromJson", "model.utils.decode_json.app_error", nil, "body: "+str, http.StatusInternalServerError)
   105  	}
   106  }
   107  
   108  func NewAppError(where string, id string, params map[string]interface{}, details string, status int) *AppError {
   109  	ap := &AppError{}
   110  	ap.Id = id
   111  	ap.params = params
   112  	ap.Message = id
   113  	ap.Where = where
   114  	ap.DetailedError = details
   115  	ap.StatusCode = status
   116  	ap.IsOAuth = false
   117  	ap.Translate(translateFunc)
   118  	return ap
   119  }
   120  
   121  var encoding = base32.NewEncoding("ybndrfg8ejkmcpqxot1uwisza345h769")
   122  
   123  // NewId is a globally unique identifier.  It is a [A-Z0-9] string 26
   124  // characters long.  It is a UUID version 4 Guid that is zbased32 encoded
   125  // with the padding stripped off.
   126  func NewId() string {
   127  	var b bytes.Buffer
   128  	encoder := base32.NewEncoder(encoding, &b)
   129  	encoder.Write(uuid.NewRandom())
   130  	encoder.Close()
   131  	b.Truncate(26) // removes the '==' padding
   132  	return b.String()
   133  }
   134  
   135  func NewRandomString(length int) string {
   136  	var b bytes.Buffer
   137  	str := make([]byte, length+8)
   138  	rand.Read(str)
   139  	encoder := base32.NewEncoder(encoding, &b)
   140  	encoder.Write(str)
   141  	encoder.Close()
   142  	b.Truncate(length) // removes the '==' padding
   143  	return b.String()
   144  }
   145  
   146  // GetMillis is a convience method to get milliseconds since epoch.
   147  func GetMillis() int64 {
   148  	return time.Now().UnixNano() / int64(time.Millisecond)
   149  }
   150  
   151  func CopyStringMap(originalMap map[string]string) map[string]string {
   152  	copyMap := make(map[string]string)
   153  	for k, v := range originalMap {
   154  		copyMap[k] = v
   155  	}
   156  	return copyMap
   157  }
   158  
   159  // MapToJson converts a map to a json string
   160  func MapToJson(objmap map[string]string) string {
   161  	b, _ := json.Marshal(objmap)
   162  	return string(b)
   163  }
   164  
   165  // MapToJson converts a map to a json string
   166  func MapBoolToJson(objmap map[string]bool) string {
   167  	b, _ := json.Marshal(objmap)
   168  	return string(b)
   169  }
   170  
   171  // MapFromJson will decode the key/value pair map
   172  func MapFromJson(data io.Reader) map[string]string {
   173  	decoder := json.NewDecoder(data)
   174  
   175  	var objmap map[string]string
   176  	if err := decoder.Decode(&objmap); err != nil {
   177  		return make(map[string]string)
   178  	} else {
   179  		return objmap
   180  	}
   181  }
   182  
   183  // MapFromJson will decode the key/value pair map
   184  func MapBoolFromJson(data io.Reader) map[string]bool {
   185  	decoder := json.NewDecoder(data)
   186  
   187  	var objmap map[string]bool
   188  	if err := decoder.Decode(&objmap); err != nil {
   189  		return make(map[string]bool)
   190  	} else {
   191  		return objmap
   192  	}
   193  }
   194  
   195  func ArrayToJson(objmap []string) string {
   196  	b, _ := json.Marshal(objmap)
   197  	return string(b)
   198  }
   199  
   200  func ArrayFromJson(data io.Reader) []string {
   201  	decoder := json.NewDecoder(data)
   202  
   203  	var objmap []string
   204  	if err := decoder.Decode(&objmap); err != nil {
   205  		return make([]string, 0)
   206  	} else {
   207  		return objmap
   208  	}
   209  }
   210  
   211  func ArrayFromInterface(data interface{}) []string {
   212  	stringArray := []string{}
   213  
   214  	dataArray, ok := data.([]interface{})
   215  	if !ok {
   216  		return stringArray
   217  	}
   218  
   219  	for _, v := range dataArray {
   220  		if str, ok := v.(string); ok {
   221  			stringArray = append(stringArray, str)
   222  		}
   223  	}
   224  
   225  	return stringArray
   226  }
   227  
   228  func StringInterfaceToJson(objmap map[string]interface{}) string {
   229  	b, _ := json.Marshal(objmap)
   230  	return string(b)
   231  }
   232  
   233  func StringInterfaceFromJson(data io.Reader) map[string]interface{} {
   234  	decoder := json.NewDecoder(data)
   235  
   236  	var objmap map[string]interface{}
   237  	if err := decoder.Decode(&objmap); err != nil {
   238  		return make(map[string]interface{})
   239  	} else {
   240  		return objmap
   241  	}
   242  }
   243  
   244  func StringToJson(s string) string {
   245  	b, _ := json.Marshal(s)
   246  	return string(b)
   247  }
   248  
   249  func StringFromJson(data io.Reader) string {
   250  	decoder := json.NewDecoder(data)
   251  
   252  	var s string
   253  	if err := decoder.Decode(&s); err != nil {
   254  		return ""
   255  	} else {
   256  		return s
   257  	}
   258  }
   259  
   260  func GetServerIpAddress() string {
   261  	if addrs, err := net.InterfaceAddrs(); err != nil {
   262  		return ""
   263  	} else {
   264  		for _, addr := range addrs {
   265  
   266  			if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() {
   267  				if ip.IP.To4() != nil {
   268  					return ip.IP.String()
   269  				}
   270  			}
   271  		}
   272  	}
   273  
   274  	return ""
   275  }
   276  
   277  func IsLower(s string) bool {
   278  	return strings.ToLower(s) == s
   279  }
   280  
   281  func IsValidEmail(email string) bool {
   282  	if !IsLower(email) {
   283  		return false
   284  	}
   285  
   286  	if addr, err := mail.ParseAddress(email); err != nil {
   287  		return false
   288  	} else if addr.Name != "" {
   289  		// mail.ParseAddress accepts input of the form "Billy Bob <billy@example.com>" which we don't allow
   290  		return false
   291  	}
   292  
   293  	return true
   294  }
   295  
   296  var reservedName = []string{
   297  	"signup",
   298  	"login",
   299  	"admin",
   300  	"channel",
   301  	"post",
   302  	"api",
   303  	"oauth",
   304  }
   305  
   306  func IsValidChannelIdentifier(s string) bool {
   307  
   308  	if !IsValidAlphaNumHyphenUnderscore(s, true) {
   309  		return false
   310  	}
   311  
   312  	if len(s) < CHANNEL_NAME_MIN_LENGTH {
   313  		return false
   314  	}
   315  
   316  	return true
   317  }
   318  
   319  func IsValidAlphaNum(s string) bool {
   320  	validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`)
   321  
   322  	return validAlphaNum.MatchString(s)
   323  }
   324  
   325  func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool {
   326  	if withFormat {
   327  		validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`)
   328  		return validAlphaNumHyphenUnderscore.MatchString(s)
   329  	}
   330  
   331  	validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
   332  	return validSimpleAlphaNumHyphenUnderscore.MatchString(s)
   333  }
   334  
   335  func Etag(parts ...interface{}) string {
   336  
   337  	etag := CurrentVersion
   338  
   339  	for _, part := range parts {
   340  		etag += fmt.Sprintf(".%v", part)
   341  	}
   342  
   343  	return etag
   344  }
   345  
   346  var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`)
   347  var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`)
   348  var hashtagStart = regexp.MustCompile(`^#{2,}`)
   349  var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`)
   350  
   351  func ParseHashtags(text string) (string, string) {
   352  	words := strings.Fields(text)
   353  
   354  	hashtagString := ""
   355  	plainString := ""
   356  	for _, word := range words {
   357  		// trim off surrounding punctuation
   358  		word = puncStart.ReplaceAllString(word, "")
   359  		word = puncEnd.ReplaceAllString(word, "")
   360  
   361  		// and remove extra pound #s
   362  		word = hashtagStart.ReplaceAllString(word, "#")
   363  
   364  		if validHashtag.MatchString(word) {
   365  			hashtagString += " " + word
   366  		} else {
   367  			plainString += " " + word
   368  		}
   369  	}
   370  
   371  	if len(hashtagString) > 1000 {
   372  		hashtagString = hashtagString[:999]
   373  		lastSpace := strings.LastIndex(hashtagString, " ")
   374  		if lastSpace > -1 {
   375  			hashtagString = hashtagString[:lastSpace]
   376  		} else {
   377  			hashtagString = ""
   378  		}
   379  	}
   380  
   381  	return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString)
   382  }
   383  
   384  func IsFileExtImage(ext string) bool {
   385  	ext = strings.ToLower(ext)
   386  	for _, imgExt := range IMAGE_EXTENSIONS {
   387  		if ext == imgExt {
   388  			return true
   389  		}
   390  	}
   391  	return false
   392  }
   393  
   394  func GetImageMimeType(ext string) string {
   395  	ext = strings.ToLower(ext)
   396  	if len(IMAGE_MIME_TYPES[ext]) == 0 {
   397  		return "image"
   398  	} else {
   399  		return IMAGE_MIME_TYPES[ext]
   400  	}
   401  }
   402  
   403  func ClearMentionTags(post string) string {
   404  	post = strings.Replace(post, "<mention>", "", -1)
   405  	post = strings.Replace(post, "</mention>", "", -1)
   406  	return post
   407  }
   408  
   409  func IsValidHttpUrl(rawUrl string) bool {
   410  	if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 {
   411  		return false
   412  	}
   413  
   414  	if _, err := url.ParseRequestURI(rawUrl); err != nil {
   415  		return false
   416  	}
   417  
   418  	return true
   419  }
   420  
   421  func IsValidTurnOrStunServer(rawUri string) bool {
   422  	if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 {
   423  		return false
   424  	}
   425  
   426  	if _, err := url.ParseRequestURI(rawUri); err != nil {
   427  		return false
   428  	}
   429  
   430  	return true
   431  }
   432  
   433  func IsSafeLink(link *string) bool {
   434  	if link != nil {
   435  		if IsValidHttpUrl(*link) {
   436  			return true
   437  		} else if strings.HasPrefix(*link, "/") {
   438  			return true
   439  		} else {
   440  			return false
   441  		}
   442  	}
   443  
   444  	return true
   445  }
   446  
   447  func IsValidWebsocketUrl(rawUrl string) bool {
   448  	if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 {
   449  		return false
   450  	}
   451  
   452  	if _, err := url.ParseRequestURI(rawUrl); err != nil {
   453  		return false
   454  	}
   455  
   456  	return true
   457  }
   458  
   459  func IsValidTrueOrFalseString(value string) bool {
   460  	return value == "true" || value == "false"
   461  }
   462  
   463  func IsValidNumberString(value string) bool {
   464  	if _, err := strconv.Atoi(value); err != nil {
   465  		return false
   466  	}
   467  
   468  	return true
   469  }
   470  
   471  func IsValidId(value string) bool {
   472  	if len(value) != 26 {
   473  		return false
   474  	}
   475  
   476  	for _, r := range value {
   477  		if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
   478  			return false
   479  		}
   480  	}
   481  
   482  	return true
   483  }
   484  
   485  // checkNowhereNil checks that the given interface value is not nil, and if a struct, that all of
   486  // its public fields are also nowhere nil
   487  func checkNowhereNil(t *testing.T, name string, value interface{}) bool {
   488  	if value == nil {
   489  		return false
   490  	}
   491  
   492  	v := reflect.ValueOf(value)
   493  	switch v.Type().Kind() {
   494  	case reflect.Ptr:
   495  		if v.IsNil() {
   496  			t.Logf("%s was nil", name)
   497  			return false
   498  		}
   499  
   500  		return checkNowhereNil(t, fmt.Sprintf("(*%s)", name), v.Elem().Interface())
   501  
   502  	case reflect.Map:
   503  		if v.IsNil() {
   504  			t.Logf("%s was nil", name)
   505  			return false
   506  		}
   507  
   508  		// Don't check map values
   509  		return true
   510  
   511  	case reflect.Struct:
   512  		nowhereNil := true
   513  		for i := 0; i < v.NumField(); i++ {
   514  			f := v.Field(i)
   515  			// Ignore unexported fields
   516  			if v.Type().Field(i).PkgPath != "" {
   517  				continue
   518  			}
   519  
   520  			nowhereNil = nowhereNil && checkNowhereNil(t, fmt.Sprintf("%s.%s", name, v.Type().Field(i).Name), f.Interface())
   521  		}
   522  
   523  		return nowhereNil
   524  
   525  	case reflect.Array:
   526  		fallthrough
   527  	case reflect.Chan:
   528  		fallthrough
   529  	case reflect.Func:
   530  		fallthrough
   531  	case reflect.Interface:
   532  		fallthrough
   533  	case reflect.UnsafePointer:
   534  		t.Logf("unhandled field %s, type: %s", name, v.Type().Kind())
   535  		return false
   536  
   537  	default:
   538  		return true
   539  	}
   540  }