2017-04-12 08:27:57 -04:00
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2019-11-29 06:59:40 -05:00
// See LICENSE.txt for license information.
2015-06-15 03:53:32 -04:00
package model
import (
2024-02-09 11:15:03 -05:00
"bytes"
2015-12-09 18:42:48 -05:00
"crypto/rand"
2021-09-13 09:53:58 -04:00
"database/sql/driver"
2015-06-15 03:53:32 -04:00
"encoding/base32"
"encoding/json"
"fmt"
"io"
2025-07-18 06:54:51 -04:00
"maps"
2017-07-26 03:51:25 -04:00
"net"
2015-06-15 03:53:32 -04:00
"net/mail"
2015-10-30 13:36:51 -04:00
"net/url"
2022-05-10 13:29:48 -04:00
"os"
2015-06-15 03:53:32 -04:00
"regexp"
2025-07-18 06:54:51 -04:00
"slices"
2021-07-21 03:45:29 -04:00
"sort"
2015-06-15 03:53:32 -04:00
"strings"
2020-12-02 03:52:41 -05:00
"sync"
2015-06-15 03:53:32 -04:00
"time"
2017-09-15 06:56:08 -04:00
"unicode"
2016-01-20 15:36:34 -05:00
"github.com/pborman/uuid"
2021-09-13 09:53:58 -04:00
"github.com/pkg/errors"
2023-03-22 17:22:27 -04:00
2023-06-11 01:24:35 -04:00
"github.com/mattermost/mattermost/server/public/shared/i18n"
2025-04-21 13:22:15 -04:00
"github.com/mattermost/mattermost/server/public/shared/mlog"
2015-06-15 03:53:32 -04:00
)
2016-07-06 18:54:54 -04:00
const (
2024-01-09 12:04:16 -05:00
LowercaseLetters = "abcdefghijklmnopqrstuvwxyz"
UppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
NUMBERS = "0123456789"
SYMBOLS = " !\"\\#$%&'()*+,-./:;<=>?@[]^_`|~"
BinaryParamKey = "MM_BINARY_PARAMETERS"
NoTranslation = "<untranslated>"
maxPropSizeBytes = 1024 * 1024
PayloadParseError = "api.payload.parse.error"
2016-07-06 18:54:54 -04:00
)
2022-10-07 09:02:11 -04:00
var ErrMaxPropSizeExceeded = fmt . Errorf ( "max prop size of %d exceeded" , maxPropSizeBytes )
2024-09-16 18:44:32 -04:00
//msgp:ignore StringInterface StringSet
2022-07-05 02:46:50 -04:00
type StringInterface map [ string ] any
2023-05-05 02:39:00 -04:00
type StringSet map [ string ] struct { }
2024-09-16 18:44:32 -04:00
//msgp:tuple StringArray
2015-06-15 03:53:32 -04:00
type StringArray [ ] string
2023-05-05 02:39:00 -04:00
func ( ss StringSet ) Has ( val string ) bool {
_ , ok := ss [ val ]
return ok
}
func ( ss StringSet ) Add ( val string ) {
ss [ val ] = struct { } { }
}
func ( ss StringSet ) Val ( ) [ ] string {
keys := make ( [ ] string , 0 , len ( ss ) )
for k := range ss {
keys = append ( keys , k )
}
return keys
}
2020-10-01 11:48:38 -04:00
func ( sa StringArray ) Remove ( input string ) StringArray {
for index := range sa {
if sa [ index ] == input {
ret := make ( StringArray , 0 , len ( sa ) - 1 )
ret = append ( ret , sa [ : index ] ... )
return append ( ret , sa [ index + 1 : ] ... )
}
}
return sa
}
func ( sa StringArray ) Contains ( input string ) bool {
2025-07-18 06:54:51 -04:00
return slices . Contains ( sa , input )
2020-10-01 11:48:38 -04:00
}
2019-04-04 14:01:21 -04:00
func ( sa StringArray ) Equals ( input StringArray ) bool {
if len ( sa ) != len ( input ) {
return false
}
for index := range sa {
if sa [ index ] != input [ index ] {
return false
}
}
return true
}
2021-09-13 09:53:58 -04:00
// Value converts StringArray to database value
func ( sa StringArray ) Value ( ) ( driver . Value , error ) {
2022-10-07 09:02:11 -04:00
sz := 0
for i := range sa {
sz += len ( sa [ i ] )
if sz > maxPropSizeBytes {
return nil , ErrMaxPropSizeExceeded
}
}
2022-01-26 04:36:33 -05:00
j , err := json . Marshal ( sa )
if err != nil {
return nil , err
}
2022-01-27 07:47:41 -05:00
// non utf8 characters are not supported https://mattermost.atlassian.net/browse/MM-41066
2022-01-26 04:36:33 -05:00
return string ( j ) , err
2021-09-13 09:53:58 -04:00
}
// Scan converts database column value to StringArray
2022-07-05 02:46:50 -04:00
func ( sa * StringArray ) Scan ( value any ) error {
2021-09-13 09:53:58 -04:00
if value == nil {
return nil
}
buf , ok := value . ( [ ] byte )
if ok {
return json . Unmarshal ( buf , sa )
}
str , ok := value . ( string )
if ok {
return json . Unmarshal ( [ ] byte ( str ) , sa )
}
return errors . New ( "received value is neither a byte slice nor string" )
}
2021-10-19 09:59:24 -04:00
// Scan converts database column value to StringMap
2022-07-05 02:46:50 -04:00
func ( m * StringMap ) Scan ( value any ) error {
2021-10-19 09:59:24 -04:00
if value == nil {
return nil
}
buf , ok := value . ( [ ] byte )
if ok {
return json . Unmarshal ( buf , m )
}
str , ok := value . ( string )
if ok {
return json . Unmarshal ( [ ] byte ( str ) , m )
}
return errors . New ( "received value is neither a byte slice nor string" )
}
2022-01-14 00:13:54 -05:00
// Value converts StringMap to database value
func ( m StringMap ) Value ( ) ( driver . Value , error ) {
2022-04-06 05:31:32 -04:00
ok := m [ BinaryParamKey ]
delete ( m , BinaryParamKey )
2022-10-07 09:02:11 -04:00
sz := 0
for k := range m {
sz += len ( k ) + len ( m [ k ] )
if sz > maxPropSizeBytes {
return nil , ErrMaxPropSizeExceeded
}
}
2022-04-06 05:31:32 -04:00
buf , err := json . Marshal ( m )
2022-01-27 07:47:41 -05:00
if err != nil {
return nil , err
}
2022-04-06 05:31:32 -04:00
if ok == "true" {
return append ( [ ] byte { 0x01 } , buf ... ) , nil
} else if ok == "false" {
return buf , nil
}
// Key wasn't found. We fall back to the default case.
return string ( buf ) , nil
2022-01-14 00:13:54 -05:00
}
2022-02-11 02:07:05 -05:00
func ( m StringMap ) MarshalJSON ( ) ( [ ] byte , error ) {
return json . Marshal ( ( map [ string ] string ) ( m ) )
}
2022-07-05 02:46:50 -04:00
func ( si * StringInterface ) Scan ( value any ) error {
2021-12-06 05:01:04 -05:00
if value == nil {
return nil
}
buf , ok := value . ( [ ] byte )
if ok {
return json . Unmarshal ( buf , si )
}
str , ok := value . ( string )
if ok {
return json . Unmarshal ( [ ] byte ( str ) , si )
}
return errors . New ( "received value is neither a byte slice nor string" )
}
2022-01-03 00:10:16 -05:00
// Value converts StringInterface to database value
func ( si StringInterface ) Value ( ) ( driver . Value , error ) {
2022-01-27 07:47:41 -05:00
j , err := json . Marshal ( si )
if err != nil {
return nil , err
}
2022-10-07 09:02:11 -04:00
if len ( j ) > maxPropSizeBytes {
return nil , ErrMaxPropSizeExceeded
}
2022-01-27 07:47:41 -05:00
// non utf8 characters are not supported https://mattermost.atlassian.net/browse/MM-41066
return string ( j ) , err
2022-01-03 00:10:16 -05:00
}
2022-09-06 23:08:41 -04:00
func ( si StringInterface ) MarshalJSON ( ) ( [ ] byte , error ) {
return json . Marshal ( ( map [ string ] any ) ( si ) )
}
2021-02-26 02:12:49 -05:00
var translateFunc i18n . TranslateFunc
2020-12-02 03:52:41 -05:00
var translateFuncOnce sync . Once
2018-01-08 13:13:24 -05:00
2021-02-26 02:12:49 -05:00
func AppErrorInit ( t i18n . TranslateFunc ) {
2020-12-02 03:52:41 -05:00
translateFuncOnce . Do ( func ( ) {
translateFunc = t
} )
2018-01-08 13:13:24 -05:00
}
2024-09-16 18:44:32 -04:00
//msgp:ignore AppError
2015-06-15 03:53:32 -04:00
type AppError struct {
2023-06-12 20:23:02 -04:00
Id string ` json:"id" `
Message string ` json:"message" ` // Message to be display to the end user without debugging information
DetailedError string ` json:"detailed_error" ` // Internal error string to help the developer
RequestId string ` json:"request_id,omitempty" ` // The RequestId that's also set in the header
StatusCode int ` json:"status_code,omitempty" ` // The http status code
Where string ` json:"-" ` // The function where it happened in the form of Struct.Func
SkipTranslation bool ` json:"-" ` // Whether translation for the error should be skipped.
params map [ string ] any
wrapped error
2015-06-15 03:53:32 -04:00
}
2023-04-21 12:53:56 -04:00
const maxErrorLength = 1024
2015-06-15 03:53:32 -04:00
func ( er * AppError ) Error ( ) string {
2022-07-28 11:05:03 -04:00
var sb strings . Builder
// render the error information
2023-12-11 04:27:51 -05:00
if er . Where != "" {
sb . WriteString ( er . Where )
sb . WriteString ( ": " )
}
2023-01-03 09:44:45 -05:00
if er . Message != NoTranslation {
sb . WriteString ( er . Message )
}
2022-07-28 11:05:03 -04:00
// only render the detailed error when it's present
if er . DetailedError != "" {
2023-01-03 09:44:45 -05:00
if er . Message != NoTranslation {
sb . WriteString ( ", " )
}
2022-07-28 11:05:03 -04:00
sb . WriteString ( er . DetailedError )
}
2022-08-18 05:01:37 -04:00
// render the wrapped error
2022-07-28 11:05:03 -04:00
err := er . wrapped
2022-08-18 05:01:37 -04:00
if err != nil {
2022-07-28 11:05:03 -04:00
sb . WriteString ( ", " )
sb . WriteString ( err . Error ( ) )
}
2023-04-21 12:53:56 -04:00
res := sb . String ( )
if len ( res ) > maxErrorLength {
res = res [ : maxErrorLength ] + "..."
}
return res
2015-06-15 03:53:32 -04:00
}
2021-02-26 02:12:49 -05:00
func ( er * AppError ) Translate ( T i18n . TranslateFunc ) {
2023-06-12 20:23:02 -04:00
if er . SkipTranslation {
return
}
2018-01-08 13:13:24 -05:00
if T == nil {
er . Message = er . Id
return
}
2016-04-22 01:37:01 -04:00
if er . params == nil {
er . Message = T ( er . Id )
} else {
er . Message = T ( er . Id , er . params )
2016-01-20 15:36:34 -05:00
}
}
2021-02-26 02:12:49 -05:00
func ( er * AppError ) SystemMessage ( T i18n . TranslateFunc ) string {
2016-04-17 20:32:20 -04:00
if er . params == nil {
return T ( er . Id )
}
2020-12-21 10:50:47 -05:00
return T ( er . Id , er . params )
2016-04-17 20:32:20 -04:00
}
2021-09-01 08:43:12 -04:00
func ( er * AppError ) ToJSON ( ) string {
2022-08-18 08:22:12 -04:00
// turn the wrapped error into a detailed message
detailed := er . DetailedError
defer func ( ) {
er . DetailedError = detailed
} ( )
er . wrappedToDetailed ( )
2018-01-30 18:23:00 -05:00
b , _ := json . Marshal ( er )
return string ( b )
2015-06-15 03:53:32 -04:00
}
2022-08-18 08:22:12 -04:00
func ( er * AppError ) wrappedToDetailed ( ) {
if er . wrapped == nil {
return
}
if er . DetailedError != "" {
er . DetailedError += ", "
}
er . DetailedError += er . wrapped . Error ( )
}
2022-07-28 11:05:03 -04:00
func ( er * AppError ) Unwrap ( ) error {
return er . wrapped
}
func ( er * AppError ) Wrap ( err error ) * AppError {
er . wrapped = err
return er
}
2024-03-20 13:30:14 -04:00
func ( er * AppError ) WipeDetailed ( ) {
er . wrapped = nil
er . DetailedError = ""
}
2024-02-09 11:15:03 -05:00
// AppErrorFromJSON will try to decode the input into an AppError.
func AppErrorFromJSON ( r io . Reader ) error {
data , err := io . ReadAll ( r )
if err != nil {
return err
2016-10-19 14:49:25 -04:00
}
2015-06-15 03:53:32 -04:00
var er AppError
2024-02-09 11:15:03 -05:00
err = json . NewDecoder ( bytes . NewReader ( data ) ) . Decode ( & er )
2020-12-21 10:50:47 -05:00
if err != nil {
2024-02-09 11:15:03 -05:00
// If the request exceeded FileSettings.MaxFileSize a plain error gets returned. Convert it into an AppError.
if string ( data ) == "http: request body too large\n" {
return errors . New ( "The request was too large. Consider asking your System Admin to raise the FileSettings.MaxFileSize setting." )
}
return errors . Wrapf ( err , "failed to decode JSON payload into AppError. Body: %s" , string ( data ) )
2015-06-15 03:53:32 -04:00
}
2024-02-09 11:15:03 -05:00
2020-12-21 10:50:47 -05:00
return & er
2015-06-15 03:53:32 -04:00
}
2022-07-05 02:46:50 -04:00
func NewAppError ( where string , id string , params map [ string ] any , details string , status int ) * AppError {
2022-07-28 11:05:03 -04:00
ap := & AppError {
Id : id ,
params : params ,
Message : id ,
Where : where ,
DetailedError : details ,
StatusCode : status ,
}
2018-01-08 13:13:24 -05:00
ap . Translate ( translateFunc )
2017-01-30 08:30:02 -05:00
return ap
}
2022-06-08 10:12:02 -04:00
var encoding = base32 . NewEncoding ( "ybndrfg8ejkmcpqxot1uwisza345h769" ) . WithPadding ( base32 . NoPadding )
2015-06-15 03:53:32 -04:00
// NewId is a globally unique identifier. It is a [A-Z0-9] string 26
// characters long. It is a UUID version 4 Guid that is zbased32 encoded
2022-06-08 10:12:02 -04:00
// without the padding.
2015-06-15 03:53:32 -04:00
func NewId ( ) string {
2022-06-08 10:12:02 -04:00
return encoding . EncodeToString ( uuid . NewRandom ( ) )
2015-06-15 03:53:32 -04:00
}
2024-07-31 10:27:52 -04:00
// NewUsername is a NewId prefixed with a letter to make valid username
func NewUsername ( ) string {
return "a" + NewId ( )
}
2019-12-04 11:31:52 -05:00
// NewRandomTeamName is a NewId that will be a valid team name.
func NewRandomTeamName ( ) string {
teamName := NewId ( )
for IsReservedTeamName ( teamName ) {
teamName = NewId ( )
}
return teamName
}
2020-04-22 12:46:16 -04:00
// NewRandomString returns a random string of the given length.
// The resulting entropy will be (5 * length) bits.
2015-12-09 18:42:48 -05:00
func NewRandomString ( length int ) string {
2020-04-22 12:46:16 -04:00
data := make ( [ ] byte , 1 + ( length * 5 / 8 ) )
rand . Read ( data )
return encoding . EncodeToString ( data ) [ : length ]
}
2026-04-08 15:49:43 -04:00
// NewTestPassword generates a password that meets complexity requirements
// (uppercase, lowercase, number, special character) with a minimum length of 14.
// The passwords are not cryptographically random. Use only in tests.
func NewTestPassword ( ) string {
const (
lowers = LowercaseLetters
uppers = UppercaseLetters
digits = NUMBERS
specials = "!%^&*(),."
all = lowers + uppers + digits + specials
minLen = PasswordFIPSMinimumLength
)
// Read all randomness in one call for performance.
// We need minLen bytes for character selection + minLen bytes for shuffle indices.
entropy := make ( [ ] byte , 2 * minLen )
if _ , err := rand . Read ( entropy ) ; err != nil {
panic ( err )
}
pw := make ( [ ] byte , minLen )
pw [ 0 ] = uppers [ int ( entropy [ 0 ] ) % len ( uppers ) ]
pw [ 1 ] = lowers [ int ( entropy [ 1 ] ) % len ( lowers ) ]
pw [ 2 ] = digits [ int ( entropy [ 2 ] ) % len ( digits ) ]
pw [ 3 ] = specials [ int ( entropy [ 3 ] ) % len ( specials ) ]
for i := 4 ; i < minLen ; i ++ {
pw [ i ] = all [ int ( entropy [ i ] ) % len ( all ) ]
}
// Shuffle to avoid predictable prefix using remaining entropy.
for i := len ( pw ) - 1 ; i > 0 ; i -- {
j := int ( entropy [ minLen + i ] ) % ( i + 1 )
pw [ i ] , pw [ j ] = pw [ j ] , pw [ i ]
}
return string ( pw )
}
2018-10-05 10:25:34 -04:00
// GetMillis is a convenience method to get milliseconds since epoch.
2015-06-15 03:53:32 -04:00
func GetMillis ( ) int64 {
2024-02-02 11:35:38 -05:00
return GetMillisForTime ( time . Now ( ) )
2015-06-15 03:53:32 -04:00
}
2018-10-05 10:25:34 -04:00
// GetMillisForTime is a convenience method to get milliseconds since epoch for provided Time.
2018-08-28 13:09:32 -04:00
func GetMillisForTime ( thisTime time . Time ) int64 {
2024-02-02 11:35:38 -05:00
return thisTime . UnixMilli ( )
2018-08-28 13:09:32 -04:00
}
2021-04-01 13:44:56 -04:00
// GetTimeForMillis is a convenience method to get time.Time for milliseconds since epoch.
func GetTimeForMillis ( millis int64 ) time . Time {
2024-02-02 11:35:38 -05:00
return time . UnixMilli ( millis )
2021-04-01 13:44:56 -04:00
}
2018-10-05 10:25:34 -04:00
// PadDateStringZeros is a convenience method to pad 2 digit date parts with zeros to meet ISO 8601 format
2018-08-28 13:09:32 -04:00
func PadDateStringZeros ( dateString string ) string {
parts := strings . Split ( dateString , "-" )
for index , part := range parts {
if len ( part ) == 1 {
parts [ index ] = "0" + part
}
}
dateString = strings . Join ( parts [ : ] , "-" )
return dateString
}
2018-10-05 10:25:34 -04:00
// GetStartOfDayMillis is a convenience method to get milliseconds since epoch for provided date's start of day
2018-08-28 13:09:32 -04:00
func GetStartOfDayMillis ( thisTime time . Time , timeZoneOffset int ) int64 {
localSearchTimeZone := time . FixedZone ( "Local Search Time Zone" , timeZoneOffset )
resultTime := time . Date ( thisTime . Year ( ) , thisTime . Month ( ) , thisTime . Day ( ) , 0 , 0 , 0 , 0 , localSearchTimeZone )
return GetMillisForTime ( resultTime )
}
2018-10-05 10:25:34 -04:00
// GetEndOfDayMillis is a convenience method to get milliseconds since epoch for provided date's end of day
2018-08-28 13:09:32 -04:00
func GetEndOfDayMillis ( thisTime time . Time , timeZoneOffset int ) int64 {
localSearchTimeZone := time . FixedZone ( "Local Search Time Zone" , timeZoneOffset )
resultTime := time . Date ( thisTime . Year ( ) , thisTime . Month ( ) , thisTime . Day ( ) , 23 , 59 , 59 , 999999999 , localSearchTimeZone )
return GetMillisForTime ( resultTime )
}
2018-04-20 08:44:18 -04:00
func CopyStringMap ( originalMap map [ string ] string ) map [ string ] string {
2021-07-21 03:45:29 -04:00
copyMap := make ( map [ string ] string , len ( originalMap ) )
2025-07-18 06:54:51 -04:00
maps . Copy ( copyMap , originalMap )
2018-04-20 08:44:18 -04:00
return copyMap
}
2021-09-01 08:43:12 -04:00
// MapToJSON converts a map to a json string
func MapToJSON ( objmap map [ string ] string ) string {
2018-01-30 18:23:00 -05:00
b , _ := json . Marshal ( objmap )
return string ( b )
2015-06-15 03:53:32 -04:00
}
2021-09-01 08:43:12 -04:00
// MapBoolToJSON converts a map to a json string
func MapBoolToJSON ( objmap map [ string ] bool ) string {
2018-01-30 18:23:00 -05:00
b , _ := json . Marshal ( objmap )
return string ( b )
2017-03-07 19:30:33 -05:00
}
2021-09-01 08:43:12 -04:00
// MapFromJSON will decode the key/value pair map
func MapFromJSON ( data io . Reader ) map [ string ] string {
2015-06-15 03:53:32 -04:00
var objmap map [ string ] string
2022-08-10 04:56:58 -04:00
json . NewDecoder ( data ) . Decode ( & objmap )
if objmap == nil {
2015-06-15 03:53:32 -04:00
return make ( map [ string ] string )
}
2022-08-10 04:56:58 -04:00
2020-12-21 10:50:47 -05:00
return objmap
2015-06-15 03:53:32 -04:00
}
2021-09-01 08:43:12 -04:00
// MapFromJSON will decode the key/value pair map
func MapBoolFromJSON ( data io . Reader ) map [ string ] bool {
2017-03-07 19:30:33 -05:00
var objmap map [ string ] bool
2022-08-10 04:56:58 -04:00
json . NewDecoder ( data ) . Decode ( & objmap )
if objmap == nil {
2017-03-07 19:30:33 -05:00
return make ( map [ string ] bool )
}
2022-08-10 04:56:58 -04:00
2020-12-21 10:50:47 -05:00
return objmap
2017-03-07 19:30:33 -05:00
}
2021-09-01 08:43:12 -04:00
func ArrayToJSON ( objmap [ ] string ) string {
2018-01-30 18:23:00 -05:00
b , _ := json . Marshal ( objmap )
return string ( b )
2015-06-15 03:53:32 -04:00
}
2024-01-09 12:04:16 -05:00
// Deprecated: ArrayFromJSON is deprecated,
// use SortedArrayFromJSON or NonSortedArrayFromJSON instead
2021-09-01 08:43:12 -04:00
func ArrayFromJSON ( data io . Reader ) [ ] string {
2015-06-15 03:53:32 -04:00
var objmap [ ] string
2022-08-10 04:56:58 -04:00
json . NewDecoder ( data ) . Decode ( & objmap )
if objmap == nil {
2015-06-15 03:53:32 -04:00
return make ( [ ] string , 0 )
2015-11-05 17:32:44 -05:00
}
2020-12-21 10:50:47 -05:00
return objmap
2015-11-05 17:32:44 -05:00
}
2024-02-21 07:13:50 -05:00
func SortedArrayFromJSON ( data io . Reader ) ( [ ] string , error ) {
2024-01-09 12:04:16 -05:00
var obj [ ] string
2024-02-21 07:13:50 -05:00
err := json . NewDecoder ( data ) . Decode ( & obj )
2024-01-09 12:04:16 -05:00
if err != nil || obj == nil {
return nil , err
}
// Remove duplicate IDs as it can bring a significant load to the database.
return RemoveDuplicateStrings ( obj ) , nil
}
2024-02-21 07:13:50 -05:00
func NonSortedArrayFromJSON ( data io . Reader ) ( [ ] string , error ) {
2024-01-09 12:04:16 -05:00
var obj [ ] string
2024-02-21 07:13:50 -05:00
err := json . NewDecoder ( data ) . Decode ( & obj )
2024-01-09 12:04:16 -05:00
if err != nil || obj == nil {
return nil , err
}
// Remove duplicate IDs, but don't sort.
return RemoveDuplicateStringsNonSort ( obj ) , nil
}
2022-07-05 02:46:50 -04:00
func ArrayFromInterface ( data any ) [ ] string {
2016-10-19 14:49:25 -04:00
stringArray := [ ] string { }
2022-07-05 02:46:50 -04:00
dataArray , ok := data . ( [ ] any )
2016-10-19 14:49:25 -04:00
if ! ok {
return stringArray
}
for _ , v := range dataArray {
if str , ok := v . ( string ) ; ok {
stringArray = append ( stringArray , str )
}
}
return stringArray
}
2022-07-05 02:46:50 -04:00
func StringInterfaceToJSON ( objmap map [ string ] any ) string {
2018-01-30 18:23:00 -05:00
b , _ := json . Marshal ( objmap )
return string ( b )
2015-11-05 17:32:44 -05:00
}
2022-07-05 02:46:50 -04:00
func StringInterfaceFromJSON ( data io . Reader ) map [ string ] any {
var objmap map [ string ] any
2022-08-10 04:56:58 -04:00
json . NewDecoder ( data ) . Decode ( & objmap )
if objmap == nil {
2022-07-05 02:46:50 -04:00
return make ( map [ string ] any )
2015-06-15 03:53:32 -04:00
}
2022-08-10 04:56:58 -04:00
2020-12-21 10:50:47 -05:00
return objmap
2015-06-15 03:53:32 -04:00
}
2024-02-21 07:13:50 -05:00
func StructFromJSONLimited [ V any ] ( data io . Reader , obj * V ) error {
err := json . NewDecoder ( data ) . Decode ( & obj )
2024-01-23 00:41:10 -05:00
if err != nil || obj == nil {
return err
}
return nil
}
2021-09-01 08:43:12 -04:00
// ToJSON serializes an arbitrary data type to JSON, discarding the error.
2022-07-05 02:46:50 -04:00
func ToJSON ( v any ) [ ] byte {
2021-04-20 13:16:40 -04:00
b , _ := json . Marshal ( v )
return b
}
2021-08-19 04:33:29 -04:00
func GetServerIPAddress ( iface string ) string {
2019-06-11 14:53:03 -04:00
var addrs [ ] net . Addr
2021-01-25 05:15:17 -05:00
if iface == "" {
2019-06-11 14:53:03 -04:00
var err error
addrs , err = net . InterfaceAddrs ( )
if err != nil {
return ""
}
2017-06-19 11:44:04 -04:00
} else {
2019-06-11 14:53:03 -04:00
interfaces , err := net . Interfaces ( )
if err != nil {
return ""
}
for _ , i := range interfaces {
if i . Name == iface {
addrs , err = i . Addrs ( )
if err != nil {
return ""
2017-06-19 11:44:04 -04:00
}
2019-06-11 14:53:03 -04:00
break
}
}
}
for _ , addr := range addrs {
if ip , ok := addr . ( * net . IPNet ) ; ok && ! ip . IP . IsLoopback ( ) && ! ip . IP . IsLinkLocalUnicast ( ) && ! ip . IP . IsLinkLocalMulticast ( ) {
if ip . IP . To4 ( ) != nil {
return ip . IP . String ( )
2017-06-19 11:44:04 -04:00
}
}
}
return ""
}
2021-07-21 03:45:29 -04:00
func isLower ( s string ) bool {
2017-10-30 12:57:24 -04:00
return strings . ToLower ( s ) == s
2015-06-15 03:53:32 -04:00
}
2025-04-21 13:22:15 -04:00
func IsValidEmail ( input string ) bool {
if ! isLower ( input ) {
2015-06-15 03:53:32 -04:00
return false
}
2025-04-21 13:22:15 -04:00
if addr , err := mail . ParseAddress ( input ) ; err != nil {
2018-08-01 15:18:14 -04:00
return false
2025-04-21 13:22:15 -04:00
} else if addr . Address != input {
// mail.ParseAddress accepts input of the form "Billy Bob <billy@example.com>" or "<billy@example.com>",
// which we don't allow. We compare the user input with the parsed addr.Address to ensure we only
// accept plain addresses like "billy@example.com"
// Log a warning for admins in case pre-existing users with emails like <billy@example.com>, which used
// to be valid before https://github.com/mattermost/mattermost/pull/29661, know how to deal with this
// error. We don't need to check for the case addr.Name != "", since that has always been rejected
if addr . Name == "" {
mlog . Warn ( "email seems to be enclosed in angle brackets, which is not valid; if this relates to an existing user, use the following mmctl command to modify their email: `mmctl user email \"<affecteduser@domain.com>\" affecteduser@domain.com`" , mlog . String ( "email" , input ) )
}
2018-08-01 15:18:14 -04:00
return false
2015-06-15 03:53:32 -04:00
}
2024-10-15 14:21:02 -04:00
// mail.ParseAddress accepts quoted strings for the address
// which can lead to sending to the wrong email address
// check for multiple '@' symbols and invalidate
2025-04-21 13:22:15 -04:00
if strings . Count ( input , "@" ) > 1 {
2024-10-15 14:21:02 -04:00
return false
}
2018-08-01 15:18:14 -04:00
return true
2015-06-15 03:53:32 -04:00
}
2015-07-08 11:50:10 -04:00
var reservedName = [ ] string {
2015-06-15 03:53:32 -04:00
"admin" ,
"api" ,
2020-04-28 10:37:19 -04:00
"channel" ,
"claim" ,
2019-05-14 16:18:33 -04:00
"error" ,
2021-02-02 16:07:28 -05:00
"files" ,
2019-05-14 16:18:33 -04:00
"help" ,
2020-04-28 10:37:19 -04:00
"landing" ,
"login" ,
"mfa" ,
"oauth" ,
"plug" ,
2020-01-14 16:39:05 -05:00
"plugins" ,
2020-04-28 10:37:19 -04:00
"post" ,
"signup" ,
2021-07-28 08:06:00 -04:00
"boards" ,
"playbooks" ,
2015-06-15 03:53:32 -04:00
}
func IsValidChannelIdentifier ( s string ) bool {
2022-04-12 11:17:15 -04:00
return validSimpleAlphaNum . MatchString ( s ) && len ( s ) >= ChannelNameMinLength
2015-06-15 03:53:32 -04:00
}
2021-07-21 03:45:29 -04:00
var (
validAlphaNum = regexp . MustCompile ( ` ^[a-z0-9]+([a-z\-0-9]+|(__)?)[a-z0-9]+$ ` )
validAlphaNumHyphenUnderscore = regexp . MustCompile ( ` ^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]+$ ` )
2022-04-12 11:17:15 -04:00
validSimpleAlphaNum = regexp . MustCompile ( ` ^[a-z0-9]+([a-z\-\_0-9]+|(__)?)[a-z0-9]*$ ` )
2021-07-21 03:45:29 -04:00
validSimpleAlphaNumHyphenUnderscore = regexp . MustCompile ( ` ^[a-zA-Z0-9\-_]+$ ` )
validSimpleAlphaNumHyphenUnderscorePlus = regexp . MustCompile ( ` ^[a-zA-Z0-9+_-]+$ ` )
)
2015-06-15 03:53:32 -04:00
2021-07-21 03:45:29 -04:00
func isValidAlphaNum ( s string ) bool {
2017-04-22 08:52:03 -04:00
return validAlphaNum . MatchString ( s )
}
2015-06-15 03:53:32 -04:00
2017-04-22 08:52:03 -04:00
func IsValidAlphaNumHyphenUnderscore ( s string , withFormat bool ) bool {
if withFormat {
return validAlphaNumHyphenUnderscore . MatchString ( s )
2015-06-15 03:53:32 -04:00
}
2017-04-22 08:52:03 -04:00
return validSimpleAlphaNumHyphenUnderscore . MatchString ( s )
2015-06-15 03:53:32 -04:00
}
2021-06-29 13:37:15 -04:00
func IsValidAlphaNumHyphenUnderscorePlus ( s string ) bool {
return validSimpleAlphaNumHyphenUnderscorePlus . MatchString ( s )
}
2022-07-05 02:46:50 -04:00
func Etag ( parts ... any ) string {
2025-11-04 06:09:11 -05:00
var etag strings . Builder
etag . WriteString ( CurrentVersion )
2015-06-15 03:53:32 -04:00
for _ , part := range parts {
2025-11-04 06:09:11 -05:00
etag . WriteString ( fmt . Sprintf ( ".%v" , part ) )
2015-06-15 03:53:32 -04:00
}
2025-11-04 06:09:11 -05:00
return etag . String ( )
2015-06-15 03:53:32 -04:00
}
2021-07-21 03:45:29 -04:00
var (
validHashtag = regexp . MustCompile ( ` ^(#\pL[\pL\d\-_.]*[\pL\d])$ ` )
puncStart = regexp . MustCompile ( ` ^[^\pL\d\s#]+ ` )
hashtagStart = regexp . MustCompile ( ` ^# { 2,} ` )
puncEnd = regexp . MustCompile ( ` [^\pL\d\s]+$ ` )
)
2015-06-15 03:53:32 -04:00
func ParseHashtags ( text string ) ( string , string ) {
2015-10-17 14:37:51 -04:00
words := strings . Fields ( text )
2015-06-15 03:53:32 -04:00
2025-11-28 10:23:51 -05:00
var hashtagStringSb strings . Builder
2025-11-04 06:09:11 -05:00
var plainString strings . Builder
2015-06-15 03:53:32 -04:00
for _ , word := range words {
2016-01-25 16:47:49 -05:00
// trim off surrounding punctuation
2015-06-15 03:53:32 -04:00
word = puncStart . ReplaceAllString ( word , "" )
word = puncEnd . ReplaceAllString ( word , "" )
2016-01-25 16:47:49 -05:00
// and remove extra pound #s
word = hashtagStart . ReplaceAllString ( word , "#" )
2015-06-15 03:53:32 -04:00
if validHashtag . MatchString ( word ) {
2025-11-28 10:23:51 -05:00
hashtagStringSb . WriteString ( " " + word )
2015-06-15 03:53:32 -04:00
} else {
2025-11-04 06:09:11 -05:00
plainString . WriteString ( " " + word )
2015-06-15 03:53:32 -04:00
}
}
2025-11-28 10:23:51 -05:00
hashtagString := hashtagStringSb . String ( )
2015-06-15 03:53:32 -04:00
if len ( hashtagString ) > 1000 {
hashtagString = hashtagString [ : 999 ]
lastSpace := strings . LastIndex ( hashtagString , " " )
if lastSpace > - 1 {
hashtagString = hashtagString [ : lastSpace ]
} else {
hashtagString = ""
}
}
2025-11-04 06:09:11 -05:00
return strings . TrimSpace ( hashtagString ) , strings . TrimSpace ( plainString . String ( ) )
2015-06-15 03:53:32 -04:00
}
func ClearMentionTags ( post string ) string {
post = strings . Replace ( post , "<mention>" , "" , - 1 )
post = strings . Replace ( post , "</mention>" , "" , - 1 )
return post
}
2021-08-16 13:46:44 -04:00
func IsValidHTTPURL ( rawURL string ) bool {
if strings . Index ( rawURL , "http://" ) != 0 && strings . Index ( rawURL , "https://" ) != 0 {
2015-10-30 13:36:51 -04:00
return false
}
2021-08-16 13:46:44 -04:00
if u , err := url . ParseRequestURI ( rawURL ) ; err != nil || u . Scheme == "" || u . Host == "" {
2015-10-30 13:36:51 -04:00
return false
}
return true
}
2016-03-10 22:14:55 -05:00
2017-09-15 06:56:08 -04:00
func IsValidId ( value string ) bool {
if len ( value ) != 26 {
return false
}
for _ , r := range value {
if ! unicode . IsLetter ( r ) && ! unicode . IsNumber ( r ) {
return false
}
}
return true
}
2018-04-13 10:56:09 -04:00
2021-07-21 03:45:29 -04:00
// RemoveDuplicateStrings does an in-place removal of duplicate strings
// from the input slice. The original slice gets modified.
2018-10-13 06:35:57 -04:00
func RemoveDuplicateStrings ( in [ ] string ) [ ] string {
2021-07-21 03:45:29 -04:00
// In-place de-dup.
// Copied from https://github.com/golang/go/wiki/SliceTricks#in-place-deduplicate-comparable
if len ( in ) == 0 {
return in
}
sort . Strings ( in )
j := 0
for i := 1 ; i < len ( in ) ; i ++ {
if in [ j ] == in [ i ] {
continue
2018-10-13 06:35:57 -04:00
}
2021-07-21 03:45:29 -04:00
j ++
in [ j ] = in [ i ]
2018-10-13 06:35:57 -04:00
}
2021-07-21 03:45:29 -04:00
return in [ : j + 1 ]
2018-10-13 06:35:57 -04:00
}
2024-01-09 12:04:16 -05:00
// RemoveDuplicateStringsNonSort does a removal of duplicate
// strings using a map.
func RemoveDuplicateStringsNonSort ( in [ ] string ) [ ] string {
allKeys := make ( map [ string ] bool )
list := [ ] string { }
for _ , item := range in {
if _ , value := allKeys [ item ] ; ! value {
allKeys [ item ] = true
list = append ( list , item )
}
}
return list
}
2018-10-13 06:35:57 -04:00
func GetPreferredTimezone ( timezone StringMap ) string {
if timezone [ "useAutomaticTimezone" ] == "true" {
return timezone [ "automaticTimezone" ]
}
return timezone [ "manualTimezone" ]
}
2019-11-18 19:02:41 -05:00
2020-04-07 16:56:07 -04:00
// SanitizeUnicode will remove undesirable Unicode characters from a string.
func SanitizeUnicode ( s string ) string {
2020-07-22 02:30:10 -04:00
return strings . Map ( filterBlocklist , s )
2020-04-07 16:56:07 -04:00
}
2020-07-22 02:30:10 -04:00
// filterBlocklist returns `r` if it is not in the blocklist, otherwise drop (-1).
// Blocklist is taken from https://www.w3.org/TR/unicode-xml/#Charlist
func filterBlocklist ( r rune ) rune {
2020-04-07 16:56:07 -04:00
const drop = - 1
switch r {
case '\u0340' , '\u0341' : // clones of grave and acute; deprecated in Unicode
return drop
case '\u17A3' , '\u17D3' : // obsolete characters for Khmer; deprecated in Unicode
return drop
case '\u2028' , '\u2029' : // line and paragraph separator
return drop
case '\u202A' , '\u202B' , '\u202C' , '\u202D' , '\u202E' : // BIDI embedding controls
return drop
case '\u206A' , '\u206B' : // activate/inhibit symmetric swapping; deprecated in Unicode
return drop
case '\u206C' , '\u206D' : // activate/inhibit Arabic form shaping; deprecated in Unicode
return drop
case '\u206E' , '\u206F' : // activate/inhibit national digit shapes; deprecated in Unicode
return drop
case '\uFFF9' , '\uFFFA' , '\uFFFB' : // interlinear annotation characters
return drop
case '\uFEFF' : // byte order mark
return drop
case '\uFFFC' : // object replacement character
return drop
}
// Scoping for musical notation
if r >= 0x0001D173 && r <= 0x0001D17A {
return drop
}
// Language tag code points
if r >= 0x000E0000 && r <= 0x000E007F {
return drop
}
return r
}
2022-05-10 13:29:48 -04:00
func IsCloud ( ) bool {
return os . Getenv ( "MM_CLOUD_INSTALLATION_ID" ) != ""
}
2024-05-09 14:49:02 -04:00
2024-12-19 10:47:52 -05:00
func SliceToMapKey ( s ... string ) map [ string ] any {
2024-05-09 14:49:02 -04:00
m := make ( map [ string ] any )
for i := range s {
m [ s [ i ] ] = struct { } { }
}
if len ( s ) != len ( m ) {
panic ( "duplicate keys" )
}
return m
}
2025-11-04 04:02:18 -05:00
// LimitRunes limits the number of runes in a string to the given maximum.
// It returns the potentially truncated string and a boolean indicating whether truncation occurred.
func LimitRunes ( s string , maxRunes int ) ( string , bool ) {
runes := [ ] rune ( s )
if len ( runes ) > maxRunes {
return string ( runes [ : maxRunes ] ) , true
}
return s , false
}
// LimitBytes limits the number of bytes in a string to the given maximum.
// It returns the potentially truncated string and a boolean indicating whether truncation occurred.
func LimitBytes ( s string , maxBytes int ) ( string , bool ) {
if len ( s ) > maxBytes {
return s [ : maxBytes ] , true
}
return s , false
}