github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/model/utils.go (about)

     1  // Copyright (c) 2015-present Xenia, 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/xzl8028/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(iface string) string {
   306  	var addrs []net.Addr
   307  	if len(iface) == 0 {
   308  		var err error
   309  		addrs, err = net.InterfaceAddrs()
   310  		if err != nil {
   311  			return ""
   312  		}
   313  	} else {
   314  		interfaces, err := net.Interfaces()
   315  		if err != nil {
   316  			return ""
   317  		}
   318  		for _, i := range interfaces {
   319  			if i.Name == iface {
   320  				addrs, err = i.Addrs()
   321  				if err != nil {
   322  					return ""
   323  				}
   324  				break
   325  			}
   326  		}
   327  	}
   328  
   329  	for _, addr := range addrs {
   330  
   331  		if ip, ok := addr.(*net.IPNet); ok && !ip.IP.IsLoopback() && !ip.IP.IsLinkLocalUnicast() && !ip.IP.IsLinkLocalMulticast() {
   332  			if ip.IP.To4() != nil {
   333  				return ip.IP.String()
   334  			}
   335  		}
   336  	}
   337  
   338  	return ""
   339  }
   340  
   341  func IsLower(s string) bool {
   342  	return strings.ToLower(s) == s
   343  }
   344  
   345  func IsValidEmail(email string) bool {
   346  	if !IsLower(email) {
   347  		return false
   348  	}
   349  
   350  	if addr, err := mail.ParseAddress(email); err != nil {
   351  		return false
   352  	} else if addr.Name != "" {
   353  		// mail.ParseAddress accepts input of the form "Billy Bob <billy@example.com>" which we don't allow
   354  		return false
   355  	}
   356  
   357  	return true
   358  }
   359  
   360  var reservedName = []string{
   361  	"signup",
   362  	"login",
   363  	"admin",
   364  	"channel",
   365  	"post",
   366  	"api",
   367  	"oauth",
   368  	"error",
   369  	"help",
   370  }
   371  
   372  func IsValidChannelIdentifier(s string) bool {
   373  
   374  	if !IsValidAlphaNumHyphenUnderscore(s, true) {
   375  		return false
   376  	}
   377  
   378  	if len(s) < CHANNEL_NAME_MIN_LENGTH {
   379  		return false
   380  	}
   381  
   382  	return true
   383  }
   384  
   385  func IsValidAlphaNum(s string) bool {
   386  	validAlphaNum := regexp.MustCompile(`^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$`)
   387  
   388  	return validAlphaNum.MatchString(s)
   389  }
   390  
   391  func IsValidAlphaNumHyphenUnderscore(s string, withFormat bool) bool {
   392  	if withFormat {
   393  		validAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$`)
   394  		return validAlphaNumHyphenUnderscore.MatchString(s)
   395  	}
   396  
   397  	validSimpleAlphaNumHyphenUnderscore := regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`)
   398  	return validSimpleAlphaNumHyphenUnderscore.MatchString(s)
   399  }
   400  
   401  func Etag(parts ...interface{}) string {
   402  
   403  	etag := CurrentVersion
   404  
   405  	for _, part := range parts {
   406  		etag += fmt.Sprintf(".%v", part)
   407  	}
   408  
   409  	return etag
   410  }
   411  
   412  var validHashtag = regexp.MustCompile(`^(#\pL[\pL\d\-_.]*[\pL\d])$`)
   413  var puncStart = regexp.MustCompile(`^[^\pL\d\s#]+`)
   414  var hashtagStart = regexp.MustCompile(`^#{2,}`)
   415  var puncEnd = regexp.MustCompile(`[^\pL\d\s]+$`)
   416  
   417  func ParseHashtags(text string) (string, string) {
   418  	words := strings.Fields(text)
   419  
   420  	hashtagString := ""
   421  	plainString := ""
   422  	for _, word := range words {
   423  		// trim off surrounding punctuation
   424  		word = puncStart.ReplaceAllString(word, "")
   425  		word = puncEnd.ReplaceAllString(word, "")
   426  
   427  		// and remove extra pound #s
   428  		word = hashtagStart.ReplaceAllString(word, "#")
   429  
   430  		if validHashtag.MatchString(word) {
   431  			hashtagString += " " + word
   432  		} else {
   433  			plainString += " " + word
   434  		}
   435  	}
   436  
   437  	if len(hashtagString) > 1000 {
   438  		hashtagString = hashtagString[:999]
   439  		lastSpace := strings.LastIndex(hashtagString, " ")
   440  		if lastSpace > -1 {
   441  			hashtagString = hashtagString[:lastSpace]
   442  		} else {
   443  			hashtagString = ""
   444  		}
   445  	}
   446  
   447  	return strings.TrimSpace(hashtagString), strings.TrimSpace(plainString)
   448  }
   449  
   450  func IsFileExtImage(ext string) bool {
   451  	ext = strings.ToLower(ext)
   452  	for _, imgExt := range IMAGE_EXTENSIONS {
   453  		if ext == imgExt {
   454  			return true
   455  		}
   456  	}
   457  	return false
   458  }
   459  
   460  func GetImageMimeType(ext string) string {
   461  	ext = strings.ToLower(ext)
   462  	if len(IMAGE_MIME_TYPES[ext]) == 0 {
   463  		return "image"
   464  	} else {
   465  		return IMAGE_MIME_TYPES[ext]
   466  	}
   467  }
   468  
   469  func ClearMentionTags(post string) string {
   470  	post = strings.Replace(post, "<mention>", "", -1)
   471  	post = strings.Replace(post, "</mention>", "", -1)
   472  	return post
   473  }
   474  
   475  func IsValidHttpUrl(rawUrl string) bool {
   476  	if strings.Index(rawUrl, "http://") != 0 && strings.Index(rawUrl, "https://") != 0 {
   477  		return false
   478  	}
   479  
   480  	if _, err := url.ParseRequestURI(rawUrl); err != nil {
   481  		return false
   482  	}
   483  
   484  	return true
   485  }
   486  
   487  func IsValidTurnOrStunServer(rawUri string) bool {
   488  	if strings.Index(rawUri, "turn:") != 0 && strings.Index(rawUri, "stun:") != 0 {
   489  		return false
   490  	}
   491  
   492  	if _, err := url.ParseRequestURI(rawUri); err != nil {
   493  		return false
   494  	}
   495  
   496  	return true
   497  }
   498  
   499  func IsSafeLink(link *string) bool {
   500  	if link != nil {
   501  		if IsValidHttpUrl(*link) {
   502  			return true
   503  		} else if strings.HasPrefix(*link, "/") {
   504  			return true
   505  		} else {
   506  			return false
   507  		}
   508  	}
   509  
   510  	return true
   511  }
   512  
   513  func IsValidWebsocketUrl(rawUrl string) bool {
   514  	if strings.Index(rawUrl, "ws://") != 0 && strings.Index(rawUrl, "wss://") != 0 {
   515  		return false
   516  	}
   517  
   518  	if _, err := url.ParseRequestURI(rawUrl); err != nil {
   519  		return false
   520  	}
   521  
   522  	return true
   523  }
   524  
   525  func IsValidTrueOrFalseString(value string) bool {
   526  	return value == "true" || value == "false"
   527  }
   528  
   529  func IsValidNumberString(value string) bool {
   530  	if _, err := strconv.Atoi(value); err != nil {
   531  		return false
   532  	}
   533  
   534  	return true
   535  }
   536  
   537  func IsValidId(value string) bool {
   538  	if len(value) != 26 {
   539  		return false
   540  	}
   541  
   542  	for _, r := range value {
   543  		if !unicode.IsLetter(r) && !unicode.IsNumber(r) {
   544  			return false
   545  		}
   546  	}
   547  
   548  	return true
   549  }
   550  
   551  // Copied from https://golang.org/src/net/dnsclient.go#L119
   552  func IsDomainName(s string) bool {
   553  	// See RFC 1035, RFC 3696.
   554  	// Presentation format has dots before every label except the first, and the
   555  	// terminal empty label is optional here because we assume fully-qualified
   556  	// (absolute) input. We must therefore reserve space for the first and last
   557  	// labels' length octets in wire format, where they are necessary and the
   558  	// maximum total length is 255.
   559  	// So our _effective_ maximum is 253, but 254 is not rejected if the last
   560  	// character is a dot.
   561  	l := len(s)
   562  	if l == 0 || l > 254 || l == 254 && s[l-1] != '.' {
   563  		return false
   564  	}
   565  
   566  	last := byte('.')
   567  	ok := false // Ok once we've seen a letter.
   568  	partlen := 0
   569  	for i := 0; i < len(s); i++ {
   570  		c := s[i]
   571  		switch {
   572  		default:
   573  			return false
   574  		case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || c == '_':
   575  			ok = true
   576  			partlen++
   577  		case '0' <= c && c <= '9':
   578  			// fine
   579  			partlen++
   580  		case c == '-':
   581  			// Byte before dash cannot be dot.
   582  			if last == '.' {
   583  				return false
   584  			}
   585  			partlen++
   586  		case c == '.':
   587  			// Byte before dot cannot be dot, dash.
   588  			if last == '.' || last == '-' {
   589  				return false
   590  			}
   591  			if partlen > 63 || partlen == 0 {
   592  				return false
   593  			}
   594  			partlen = 0
   595  		}
   596  		last = c
   597  	}
   598  	if last == '-' || partlen > 63 {
   599  		return false
   600  	}
   601  
   602  	return ok
   603  }
   604  
   605  func RemoveDuplicateStrings(in []string) []string {
   606  	out := []string{}
   607  	seen := make(map[string]bool, len(in))
   608  
   609  	for _, item := range in {
   610  		if !seen[item] {
   611  			out = append(out, item)
   612  
   613  			seen[item] = true
   614  		}
   615  	}
   616  
   617  	return out
   618  }
   619  
   620  func GetPreferredTimezone(timezone StringMap) string {
   621  	if timezone["useAutomaticTimezone"] == "true" {
   622  		return timezone["automaticTimezone"]
   623  	}
   624  
   625  	return timezone["manualTimezone"]
   626  }