OSGOS_D1/custom/server/common/routines.go

402 lines
8.5 KiB
Go
Raw Normal View History

2024-01-30 07:10:17 +00:00
// OSGOS server
// =======================================================================================================
// Author: LLC Texnico <main@texnico.ru>
// 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
}