// OSGOS server // ======================================================================================================= // Author: LLC Texnico // All rights reserved // Russia, Chelyabinsk, 2024 package common import ( "bytes" "crypto/aes" "crypto/cipher" "crypto/rand" "crypto/sha256" "crypto/tls" "encoding/base64" "encoding/json" "errors" "fmt" "github.com/disintegration/imaging" log "github.com/sirupsen/logrus" "gopkg.in/gomail.v2" "image" "io" mRand "math/rand" "os" "regexp" "sync" "time" ) var ( mutexRandom sync.Mutex counterRandom int64 ) func init() { LoadOptions() log.SetFormatter(&log.TextFormatter{ DisableQuote: true, }) log.SetLevel(log.InfoLevel) logFile, _ = os.OpenFile(LogName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm) log.SetOutput(io.MultiWriter(logFile, os.Stdout)) } func RotateLogFiles() { fs, err := os.Stat(LogName) if err != nil { return } if fs.Size() > maxLogFileMb*1024*1024 { log.SetOutput(os.Stdout) err = os.Rename(LogName, LogName+".old") if err != nil { return } logFile, _ := os.OpenFile(LogName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) log.SetOutput(io.MultiWriter(logFile, os.Stdout)) log.Infof("truncate log file") } } func GetDigest(text string) string { h := sha256.New() h.Write([]byte(text)) return fmt.Sprintf("%x", h.Sum(nil)) } func RandomString(l int) string { mutexRandom.Lock() counterRandom++ mRand.Seed(time.Now().UTC().UnixNano() + counterRandom) var result bytes.Buffer var temp string for i := 0; i < l; { t := RandInt(0, 3) if t == 1 { temp = string(rune(RandInt(48, 58))) } else if t == 2 { temp = string(rune(RandInt(65, 91))) } else { temp = string(rune(RandInt(97, 123))) } result.WriteString(temp) i++ } mutexRandom.Unlock() return result.String() } func RandomStringOnlyAlphabetic(l int) string { mutexRandom.Lock() counterRandom++ mRand.Seed(time.Now().UTC().UnixNano() + counterRandom) var result bytes.Buffer var temp string for i := 0; i < l; { t := RandInt(0, 2) if t == 1 { temp = string(rune(RandInt(97, 123))) } else { temp = string(rune(RandInt(65, 91))) } result.WriteString(temp) i++ } mutexRandom.Unlock() return result.String() } func RandFloat(min float64, max float64) float64 { return min + mRand.Float64()*(max-min) } func RandInt(min int, max int) int { return min + mRand.Intn(max-min) } func Copy(src, dst string) error { in, err := os.Open(src) if err != nil { return err } defer in.Close() out, err := os.Create(dst) if err != nil { return err } defer out.Close() _, err = io.Copy(out, in) if err != nil { return err } return out.Close() } func EncryptAES(key, text []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } ciphertext := make([]byte, aes.BlockSize+len(text)) iv := ciphertext[:aes.BlockSize] if _, err := io.ReadFull(rand.Reader, iv); err != nil { return nil, err } cfb := cipher.NewCFBEncrypter(block, iv) cfb.XORKeyStream(ciphertext[aes.BlockSize:], text) return ciphertext, nil } func DecryptAES(key, text []byte) ([]byte, error) { block, err := aes.NewCipher(key) if err != nil { return nil, err } if len(text) < aes.BlockSize { return nil, errors.New("ciphertext too short") } iv := text[:aes.BlockSize] text = text[aes.BlockSize:] cfb := cipher.NewCFBDecrypter(block, iv) cfb.XORKeyStream(text, text) return text, nil } func UnpackSessionFromDigest(digest string, data interface{}) error { dataEncrypted, err := base64.StdEncoding.DecodeString(digest) if err != nil { return err } dataDecrypted, err := DecryptAES([]byte(Options.Key), dataEncrypted) if err != nil { return err } err = json.Unmarshal(dataDecrypted, data) if err != nil { return err } return nil } func PackSessionToDigest(data interface{}) (digest string, err error) { dataByte, err := json.Marshal(data) if err != nil { return "", err } dataEncrypted, err := EncryptAES([]byte(Options.Key), dataByte) if err != nil { return "", err } dataBase64 := base64.StdEncoding.EncodeToString(dataEncrypted) return string(dataBase64), nil } func SendEmail(to string, html bool, body string, filename string, filebody []byte) (bool, error) { d := gomail.NewDialer(Options.ServerSMTP, Options.PortSMTP, Options.LoginSMTP, Options.PassSMTP) d.TLSConfig = &tls.Config{InsecureSkipVerify: true} m := gomail.NewMessage() m.SetHeader("From", Options.LoginSMTP) m.SetHeader("To", to) m.SetHeader("Subject", TemplateNameCompany) if html { m.SetBody("text/html; charset=utf-8", body) } else { m.SetBody("text/plain", body) } if len(filename) > 0 { m.Attach(filename, gomail.SetCopyFunc(func(w io.Writer) error { _, err := w.Write(filebody) return err })) } // Send the email to Bob, Cora and Dan. if err := d.DialAndSend(m); err != nil { return false, err } return true, nil } func SaveFile(name string, data interface{}) error { b, err := json.MarshalIndent(data, "", "\t") if err == nil { _ = os.Remove(fmt.Sprintf("%s.tmp", name)) f, err := os.Create(fmt.Sprintf("%s.tmp", name)) if err == nil { n, err := f.Write(b) if n == len(b) && err == nil { _ = f.Close() if Options.Backup { if _, err := os.Stat(fmt.Sprintf("%s.%d", name, rotationNumFiles)); err != nil { _ = os.Remove(fmt.Sprintf("%s.%d", name, rotationNumFiles)) } for i := rotationNumFiles - 1; i >= 0; i-- { if _, err := os.Stat(fmt.Sprintf("%s.%d", name, i)); err != nil { continue } _ = os.Rename(fmt.Sprintf("%s.%d", name, i), fmt.Sprintf("%s.%d", name, i+1)) } _ = os.Rename(name, fmt.Sprintf("%s.%d", name, 0)) } else { _ = os.Remove(name) } _ = os.Rename(fmt.Sprintf("%s.tmp", name), name) } else { _ = f.Close() log.Errorf("error saving %s: %v", name, err) return err } } else { log.Errorf("error saving %s: %v", name, err) return err } } else { log.Errorf("error saving %s: %v", name, err) return err } return nil } func LoadFile(name string, data interface{}) error { f, err := os.Open(name) defer func() { _ = f.Close() }() if err == nil { b, err := io.ReadAll(f) if err == nil { err = json.Unmarshal(b, data) if err != nil { log.Errorf("error loading %s: %v", name, err) return err } } else { log.Errorf("error loading %s: %v", name, err) return err } } else { log.Errorf("error loading %s: %v", name, err) return err } return nil } func SaveOptions() { _ = SaveFile(OptionsFile, Options) } func LoadOptions() bool { err := LoadFile(OptionsFile, &Options) log.SetLevel(Options.TypeLog) if Options.TypeLog == log.DebugLevel { log.SetOutput(nil) logFile, _ := os.OpenFile(LogName, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm) log.SetOutput(io.MultiWriter(logFile, os.Stdout)) log.Infof("truncate log file") } if err != nil { return false } return true } func CleanLog() bool { log.SetOutput(os.Stdout) err := logFile.Close() if err != nil { log.Error(err.Error()) return false } err = os.Remove(LogName) if err != nil { log.Error(err.Error()) return false } logFile, err = os.OpenFile(LogName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.ModePerm) if err != nil { log.Error(err.Error()) return false } log.SetOutput(logFile) return true } func DeepCopy[T any](source T) T { b, _ := json.Marshal(source) var d T _ = json.Unmarshal(b, &d) return d } func ValidationPass(pass string) error { if len(pass) < 7 { return fmt.Errorf("слишком короткий") } if len(pass) > 100 { return fmt.Errorf("слишком длинный") } isNumeric := false for i := range pass { if '0' <= pass[i] && pass[i] <= '9' { isNumeric = true } } isAlpha := false for i := range pass { if ('a' <= pass[i] && pass[i] <= 'z') || ('A' <= pass[i] && pass[i] <= 'Z') { isAlpha = true } } if !isNumeric { return fmt.Errorf("не содержит числа") } if !isAlpha { return fmt.Errorf("не содержит латинской буквы") } return nil } func ValidationEmail(email string) bool { if !regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$").MatchString(email) { return false } return true } func ResizeImageTo(img image.Image, widthSizeLimit int) image.Image { if widthSizeLimit > 0 { if img.Bounds().Size().X > widthSizeLimit { img = imaging.Resize(img, widthSizeLimit, widthSizeLimit/img.Bounds().Size().X/img.Bounds().Size().Y, imaging.Lanczos) } } return img }