github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/model/utils.go (about)

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