402 lines
8.5 KiB
Go
402 lines
8.5 KiB
Go
|
// 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
|
||
|
}
|