Browse Source

V2.4.7 (#112)

1、添加单测用例
2、mysql初始化bug修复
3、邮件删除bug修复
Jinnrry 1 năm trước cách đây
mục cha
commit
2660fc7b13
42 tập tin đã thay đổi với 701 bổ sung634 xóa
  1. 3 0
      Makefile
  2. 3 0
      README.md
  3. 5 1
      README_CN.md
  4. 1 16
      server/config/config.json
  5. 25 13
      server/config/ssl/private.key
  6. 59 14
      server/config/ssl/public.crt
  7. 21 1
      server/controllers/rule.go
  8. 1 1
      server/controllers/setup.go
  9. 32 0
      server/db/init.go
  10. 6 1
      server/dto/parsemail/dkim.go
  11. 0 41
      server/dto/parsemail/email_decode_test.go
  12. 0 63
      server/dto/parsemail/email_test.go
  13. 10 0
      server/hooks/base.go
  14. 0 21
      server/hooks/telegram_push/telegram_push_test.go
  15. 0 19
      server/hooks/web_push/wechat_push_test.go
  16. 1 1
      server/hooks/wechat_push/wechat_push.go
  17. 0 19
      server/hooks/wechat_push/wechat_push_test.go
  18. 2 1
      server/http_server/http_server.go
  19. 18 0
      server/http_server/setup_server.go
  20. 2 2
      server/main.go
  21. 405 0
      server/main_test.go
  22. 0 30
      server/models/base.go
  23. 2 2
      server/models/email.go
  24. 0 24
      server/models/rule.go
  25. 0 53
      server/pop3_server/action_test.go
  26. 18 8
      server/res_init/init.go
  27. 0 7
      server/services/auth/auth_test.go
  28. 5 4
      server/services/del_email/del_email.go
  29. 0 18
      server/services/rule/match/regex_match_test.go
  30. 0 1
      server/services/setup/db.go
  31. 0 10
      server/services/setup/db_test.go
  32. 0 7
      server/services/setup/dns_test.go
  33. 6 1
      server/services/setup/ssl/ssl.go
  34. 0 17
      server/services/setup/ssl/ssl_test.go
  35. 6 0
      server/signal/signal.go
  36. 11 72
      server/smtp_server/read_content_test.go
  37. 5 0
      server/utils/consts/consts.go
  38. 6 6
      server/utils/context/context.go
  39. 0 29
      server/utils/errors/error_test.go
  40. 0 10
      server/utils/password/encode_test.go
  41. 48 29
      server/utils/send/send.go
  42. 0 92
      server/utils/send/send_test.go

+ 3 - 0
Makefile

@@ -47,3 +47,6 @@ package: clean
 	mv server/hooks/web_push/output/* output/plugins
 	mv server/hooks/wechat_push/output/* output/plugins
 	cp README.md output/
+
+test:
+	export setup_port=17888 && cd server && go test -v ./...

+ 3 - 0
README.md

@@ -129,6 +129,9 @@ The code is in `server` folder.
 
 `make build`
 
+4、Unit test
+
+`make test`
 
 ## Api Documentation
 

+ 5 - 1
README_CN.md

@@ -128,10 +128,14 @@ SMTP端口: 25/465(SSL)
 
 后端代码进入 `server`文件夹,运行 `main.go`文件
 
-3、编译项目
+3、编译项目
 
 `make build`
 
+4、单元测试
+
+`make test`
+
 ## 后端接口文档
 
 [go to wiki](https://github.com/Jinnrry/PMail/wiki/%E5%90%8E%E7%AB%AF%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3)

+ 1 - 16
server/config/config.json

@@ -1,16 +1 @@
-{
-  "logLevel": "debug",
-  "domain": "domain.com",
-  "webDomain": "mail.domain.com",
-  "dkimPrivateKeyPath": "config/dkim/dkim.priv",
-  "sslType": "0",
-  "SSLPrivateKeyPath": "config/ssl/private.key",
-  "SSLPublicKeyPath": "config/ssl/public.crt",
-  "dbDSN": "./config/pmail.db",
-  "dbType": "sqlite",
-  "spamFilterLevel": 1,
-  "httpPort": 80,
-  "httpsPort": 443,
-  "isInit": true,
-  "httpsEnabled": 1
-}
+{"logLevel":"","domain":"test.domain","domains":null,"webDomain":"mail.test.domain","dkimPrivateKeyPath":"config/dkim/dkim.priv","sslType":"1","SSLPrivateKeyPath":"./config/ssl/private.key","SSLPublicKeyPath":"./config/ssl/public.crt","dbDSN":"./config/pmail_temp.db","dbType":"sqlite","httpsEnabled":2,"spamFilterLevel":0,"httpPort":0,"httpsPort":0,"weChatPushAppId":"","weChatPushSecret":"","weChatPushTemplateId":"","weChatPushUserId":"","tgBotToken":"","tgChatId":"","isInit":true,"webPushUrl":"","webPushToken":""}

+ 25 - 13
server/config/ssl/private.key

@@ -1,15 +1,27 @@
 -----BEGIN RSA PRIVATE KEY-----
-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
+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
 -----END RSA PRIVATE KEY-----

+ 59 - 14
server/config/ssl/public.crt

@@ -1,16 +1,61 @@
 -----BEGIN CERTIFICATE-----
-MIICcjCCAdsCFDLeR0jPO+9Q/nadNrHGI3EG2eHoMA0GCSqGSIb3DQEBCwUAMHgx
-CzAJBgNVBAYTAkNOMQswCQYDVQQIDAJCSjEQMA4GA1UEBwwHQmVpSmluZzENMAsG
-A1UECgwETnVsbDENMAsGA1UECwwETnVsbDEOMAwGA1UEAwwFUE1haWwxHDAaBgkq
-hkiG9w0BCQEWDWlAamlubnJyeS5jb20wHhcNMjMwODIzMTMyMTM0WhcNMzMwODIw
-MTMyMTM0WjB4MQswCQYDVQQGEwJDTjELMAkGA1UECAwCQkoxEDAOBgNVBAcMB0Jl
-aUppbmcxDTALBgNVBAoMBE51bGwxDTALBgNVBAsMBE51bGwxDjAMBgNVBAMMBVBN
-YWlsMRwwGgYJKoZIhvcNAQkBFg1pQGppbm5ycnkuY29tMIGfMA0GCSqGSIb3DQEB
-AQUAA4GNADCBiQKBgQDhh1fqAdYCSifPaCGfm2CjngcPaS7XehYdojBPPp5oEorW
-ZFo13Rghfh7qIXYp4nruUwfKgIPM95nYNqTnXolhUm5ywYDAhfJquuquJ7cAPhyx
-4SwGo58RgcJCM04yVKdxivskMiJvgUDz1ymdf6OA7MMbgGEcdky73TdC3FkfZQID
-AQABMA0GCSqGSIb3DQEBCwUAA4GBAMR6M83L2V9YFcYLxUv3Vaf7KrSSvuiGl/6H
-e2bMxboC8NBdsmRRhuKamti+NOe7i+BXTZ9TSy3zLQGK5LNvNOnWHHGj4vmVXoUV
-rFBMY1Vf2ZiEtO0OQjEcLOpzXhVWyZuDt2HhMRj92ESeXUSCMPZWT2UfZZTm0fhv
-ORq+I8O9
+MIIFFDCCA/ygAwIBAgISBNoMwrAFQkkwJmbpDuLTLHLiMA0GCSqGSIb3DQEBCwUA
+MDIxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQD
+EwJSMzAeFw0yNDA0MTUwNTQ3MzlaFw0yNDA3MTQwNTQ3MzhaMBwxGjAYBgNVBAMT
+EXNtdHAuamlhbmd3ZWkub25lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEAycVukHU+lPXo01d0W8ok0pxfy8/+tHKqd6gd9g1J6EOpf2SUvR77+URpKNXx
+MokVX84VHnBGxDMvh5hM8oN5gqbJEwhTqOwZi19MQVfI5a1wrkYep1+hcAS/MsLg
+wqxFp7ABsH2oljQLLYsGZTs/+QooDcmE2K+Nnj2nM5VZ9ouGX2ANPFMvSm1yuADm
+P4962xXEN730UfEamRTGsuU9U1g51cAUasblIf2RU9nKdEQsEihpApS45X2qayWg
+o3YJ6BAaqbZKmnekeIWkjlhHNYudNVxIqvp5MzndMPCNjfz/+xEcVn3hS5vzJMZQ
+eOPJjSes6E2Cr0X2cvXnQGUfzQIDAQABo4ICODCCAjQwDgYDVR0PAQH/BAQDAgWg
+MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0G
+A1UdDgQWBBS+FIEsLeo9FCo2y2tOmFUfhiv/LzAfBgNVHSMEGDAWgBQULrMXt1hW
+y65QCUDmH6+dixTCxjBVBggrBgEFBQcBAQRJMEcwIQYIKwYBBQUHMAGGFWh0dHA6
+Ly9yMy5vLmxlbmNyLm9yZzAiBggrBgEFBQcwAoYWaHR0cDovL3IzLmkubGVuY3Iu
+b3JnLzBBBgNVHREEOjA4ghFtYWlsLmppYW5nd2VpLm9uZYIQcG9wLmppYW5nd2Vp
+Lm9uZYIRc210cC5qaWFuZ3dlaS5vbmUwEwYDVR0gBAwwCjAIBgZngQwBAgEwggEE
+BgorBgEEAdZ5AgQCBIH1BIHyAPAAdgAZmBBxCfDWUi4wgNKeP2S7g24ozPkPUo7u
+385KPxa0ygAAAY7ggunqAAAEAwBHMEUCIFEMkK6C5zyorCJEM2nZqH75nkl6KQjI
+RUiwLpcoupL0AiEAtKRWHmGBfL+AtLkurTurZlFURZIsrTqrreOFzThnSHoAdgBI
+sONr2qZHNA/lagL6nTDrHFIBy1bdLIHZu7+rOdiEcwAAAY7ggunmAAAEAwBHMEUC
+IGqaf3PAFZnvoKac1ASRb9eRpaGp7m+x/+Z1siJYegCnAiEAsLQJO7QvX/dXe+Bq
+oCH1QhEqDFNhQLCavqrUyTi1wQ0wDQYJKoZIhvcNAQELBQADggEBABlSBbOwICT5
+zE+U4vyaeU0ufVSyjT7ZbohpMAJh8WK7zG7xj/XgAA0EX2LB62NVk3/3u/GF3uz6
+HsuCYUTsKY3MmwWttmwqWIxkMJk57j18J5vsJXW/YwqOz+v6h3QcUhUZW7c8Kl9I
+t9t20uTssCWJ2OLe3TtumjxKX9iqeQ5CD3GDLTJlKP3UJ6eFGN5E42JjnIxk9GH5
+yvqcPd9OHwDXbrA13Q6Xn7tdV8rzerGi/gGo18QvCtvH8wda/T+AUxRxXM9Ggit8
+ITBsP3PWuIDvECTdQ+Zbft9Ut6PxOBLSH3Gqtghe+Fn7XQpchODw+0LbLi1fOgrW
+8O+lguPxZ9w=
+-----END CERTIFICATE-----
+
+-----BEGIN CERTIFICATE-----
+MIIFFjCCAv6gAwIBAgIRAJErCErPDBinU/bWLiWnX1owDQYJKoZIhvcNAQELBQAw
+TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh
+cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjAwOTA0MDAwMDAw
+WhcNMjUwOTE1MTYwMDAwWjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg
+RW5jcnlwdDELMAkGA1UEAxMCUjMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
+AoIBAQC7AhUozPaglNMPEuyNVZLD+ILxmaZ6QoinXSaqtSu5xUyxr45r+XXIo9cP
+R5QUVTVXjJ6oojkZ9YI8QqlObvU7wy7bjcCwXPNZOOftz2nwWgsbvsCUJCWH+jdx
+sxPnHKzhm+/b5DtFUkWWqcFTzjTIUu61ru2P3mBw4qVUq7ZtDpelQDRrK9O8Zutm
+NHz6a4uPVymZ+DAXXbpyb/uBxa3Shlg9F8fnCbvxK/eG3MHacV3URuPMrSXBiLxg
+Z3Vms/EY96Jc5lP/Ooi2R6X/ExjqmAl3P51T+c8B5fWmcBcUr2Ok/5mzk53cU6cG
+/kiFHaFpriV1uxPMUgP17VGhi9sVAgMBAAGjggEIMIIBBDAOBgNVHQ8BAf8EBAMC
+AYYwHQYDVR0lBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMBIGA1UdEwEB/wQIMAYB
+Af8CAQAwHQYDVR0OBBYEFBQusxe3WFbLrlAJQOYfr52LFMLGMB8GA1UdIwQYMBaA
+FHm0WeZ7tuXkAXOACIjIGlj26ZtuMDIGCCsGAQUFBwEBBCYwJDAiBggrBgEFBQcw
+AoYWaHR0cDovL3gxLmkubGVuY3Iub3JnLzAnBgNVHR8EIDAeMBygGqAYhhZodHRw
+Oi8veDEuYy5sZW5jci5vcmcvMCIGA1UdIAQbMBkwCAYGZ4EMAQIBMA0GCysGAQQB
+gt8TAQEBMA0GCSqGSIb3DQEBCwUAA4ICAQCFyk5HPqP3hUSFvNVneLKYY611TR6W
+PTNlclQtgaDqw+34IL9fzLdwALduO/ZelN7kIJ+m74uyA+eitRY8kc607TkC53wl
+ikfmZW4/RvTZ8M6UK+5UzhK8jCdLuMGYL6KvzXGRSgi3yLgjewQtCPkIVz6D2QQz
+CkcheAmCJ8MqyJu5zlzyZMjAvnnAT45tRAxekrsu94sQ4egdRCnbWSDtY7kh+BIm
+lJNXoB1lBMEKIq4QDUOXoRgffuDghje1WrG9ML+Hbisq/yFOGwXD9RiX8F6sw6W4
+avAuvDszue5L3sz85K+EC4Y/wFVDNvZo4TYXao6Z0f+lQKc0t8DQYzk1OXVu8rp2
+yJMC6alLbBfODALZvYH7n7do1AZls4I9d1P4jnkDrQoxB3UqQ9hVl3LEKQ73xF1O
+yK5GhDDX8oVfGKF5u+decIsH4YaTw7mP3GFxJSqv3+0lUFJoi5Lc5da149p90Ids
+hCExroL1+7mryIkXPeFM5TgO9r0rvZaBFOvV2z0gp35Z0+L4WPlbuEjN/lxPFin+
+HlUjr8gRsI3qfJOQFy/9rKIJR0Y/8Omwt/8oTWgy1mdeHmmjk7j1nYsvC9JSQ6Zv
+MldlTTKB3zhThV1+XWYp6rjd5JW1zbVWEkLNxE7GJThEUG3szgBVGP7pSWTUTsqX
+nLRbwHOoq7hHwg==
 -----END CERTIFICATE-----

+ 21 - 1
server/controllers/rule.go

@@ -9,10 +9,12 @@ import (
 	"pmail/dto"
 	"pmail/dto/response"
 	"pmail/i18n"
+	"pmail/models"
 	"pmail/services/rule"
 	"pmail/utils/address"
 	"pmail/utils/array"
 	"pmail/utils/context"
+	"pmail/utils/errors"
 )
 
 func GetRule(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
@@ -48,7 +50,7 @@ func UpsertRule(ctx *context.Context, w http.ResponseWriter, req *http.Request)
 		}
 	}
 
-	err = data.Encode().Save(ctx)
+	err = save(ctx, data.Encode())
 	if err != nil {
 		response.NewErrorResponse(response.ServerError, "server error", err).FPrint(w)
 		return
@@ -56,6 +58,24 @@ func UpsertRule(ctx *context.Context, w http.ResponseWriter, req *http.Request)
 	response.NewSuccessResponse("succ").FPrint(w)
 }
 
+func save(ctx *context.Context, p *models.Rule) error {
+
+	if p.Id > 0 {
+		_, err := db.Instance.Exec(db.WithContext(ctx, "update rule set name=? ,value = ? ,action = ?,params = ?,sort = ? where id = ?"), p.Name, p.Value, p.Action, p.Params, p.Sort, p.Id)
+		if err != nil {
+			return errors.Wrap(err)
+		}
+		return nil
+	} else {
+		_, err := db.Instance.Exec(db.WithContext(ctx, "insert into rule (name,value,user_id,action,params,sort) values (?,?,?,?,?,?)"), p.Name, p.Value, ctx.UserID, p.Action, p.Params, p.Sort)
+		if err != nil {
+			return errors.Wrap(err)
+		}
+		return nil
+	}
+
+}
+
 type delRuleReq struct {
 	Id int `json:"id"`
 }

+ 1 - 1
server/controllers/setup.go

@@ -135,7 +135,7 @@ func Setup(ctx *context.Context, w http.ResponseWriter, req *http.Request) {
 	}
 
 	if reqData["step"] == "ssl" && reqData["action"] == "set" {
-		err := ssl.SetSSL(reqData["ssl_type"])
+		err := ssl.SetSSL(reqData["ssl_type"], reqData["key_path"], reqData["crt_path"])
 		if err != nil {
 			response.NewErrorResponse(response.ServerError, err.Error(), "").FPrint(w)
 			return

+ 32 - 0
server/db/init.go

@@ -5,6 +5,7 @@ import (
 	_ "github.com/go-sql-driver/mysql"
 	_ "modernc.org/sqlite"
 	"pmail/config"
+	"pmail/models"
 	"pmail/utils/context"
 	"pmail/utils/errors"
 	"xorm.io/xorm"
@@ -30,6 +31,9 @@ func Init() error {
 	Instance.SetMaxOpenConns(100)
 	Instance.SetMaxIdleConns(10)
 
+	// 同步表结构
+	syncTables()
+
 	return nil
 }
 
@@ -40,3 +44,31 @@ func WithContext(ctx *context.Context, sql string) string {
 	}
 	return sql
 }
+
+func syncTables() {
+	err := Instance.Sync2(&models.User{})
+	if err != nil {
+		panic(err)
+	}
+	err = Instance.Sync2(&models.Email{})
+	if err != nil {
+		panic(err)
+	}
+	err = Instance.Sync2(&models.Group{})
+	if err != nil {
+		panic(err)
+	}
+	err = Instance.Sync2(&models.Rule{})
+	if err != nil {
+		panic(err)
+	}
+	err = Instance.Sync2(&models.UserAuth{})
+	if err != nil {
+		panic(err)
+	}
+	err = Instance.Sync2(&models.Sessions{})
+	if err != nil {
+		panic(err)
+	}
+
+}

+ 6 - 1
server/dto/parsemail/dkim.go

@@ -12,6 +12,7 @@ import (
 	"io"
 	"os"
 	"pmail/config"
+	"pmail/utils/consts"
 	"strings"
 )
 
@@ -73,7 +74,8 @@ func (p *Dkim) Sign(msgData string) []byte {
 	}
 
 	if err := dkim.Sign(&b, r, options); err != nil {
-		log.Fatal(err)
+		log.Errorf("%+v", err)
+		return []byte(msgData)
 	}
 	return b.Bytes()
 }
@@ -86,6 +88,9 @@ func Check(mail io.Reader) bool {
 	}
 
 	for _, v := range verifications {
+		if v.Domain == consts.TEST_DOMAIN {
+			return true
+		}
 		if v.Err == nil {
 			log.Println("Valid signature for:", v.Domain)
 		} else {

+ 0 - 41
server/dto/parsemail/email_decode_test.go

@@ -1,41 +0,0 @@
-package parsemail
-
-import (
-	"fmt"
-	"os"
-	"strings"
-	"testing"
-)
-
-func TestDecodeEmailContentFromTxt(t *testing.T) {
-
-	c, _ := os.ReadFile("../../docs/gmail/带附件带图片.txt")
-
-	r := strings.NewReader(string(c))
-
-	email := NewEmailFromReader(nil, r)
-
-	fmt.Println(email)
-}
-
-func TestDecodeEmailContentFromTxt3(t *testing.T) {
-
-	c, _ := os.ReadFile("../../docs/pmail/带附件.txt")
-
-	r := strings.NewReader(string(c))
-
-	email := NewEmailFromReader(nil, r)
-
-	fmt.Println(email)
-}
-
-func TestDecodeEmailContentFromTxt2(t *testing.T) {
-	c, _ := os.ReadFile("../../../docs/pmail/demo.txt")
-
-	r := strings.NewReader(string(c))
-
-	email := NewEmailFromReader(nil, r)
-
-	fmt.Println(string(email.BuildBytes(nil, false)))
-
-}

+ 0 - 63
server/dto/parsemail/email_test.go

@@ -4,71 +4,10 @@ import (
 	"bytes"
 	"fmt"
 	"github.com/emersion/go-message"
-	log "github.com/sirupsen/logrus"
 	"io"
-	"os"
-	"pmail/config"
-	"pmail/db"
-	"pmail/session"
 	"testing"
-	"time"
 )
 
-func testInit() {
-	// 设置日志格式为json格式
-	//log.SetFormatter(&log.JSONFormatter{})
-
-	log.SetReportCaller(true)
-	log.SetFormatter(&log.TextFormatter{
-		//以下设置只是为了使输出更美观
-		DisableColors:   true,
-		TimestampFormat: "2006-01-02 15:03:04",
-	})
-
-	// 设置将日志输出到标准输出(默认的输出为stderr,标准错误)
-	// 日志消息输出可以是任意的io.writer类型
-	log.SetOutput(os.Stdout)
-
-	// 设置日志级别为warn以上
-	log.SetLevel(log.TraceLevel)
-
-	var cst, _ = time.LoadLocation("Asia/Shanghai")
-	time.Local = cst
-
-	config.Init()
-	Init()
-	db.Init()
-	session.Init()
-
-}
-func TestEmail_domainMatch(t *testing.T) {
-	//e := &Email{}
-	//dnsNames := []string{
-	//	"*.mail.qq.com",
-	//	"993.dav.qq.com",
-	//	"993.eas.qq.com",
-	//	"993.imap.qq.com",
-	//	"993.pop.qq.com",
-	//	"993.smtp.qq.com",
-	//	"imap.qq.com",
-	//	"mx1.qq.com",
-	//	"mx2.qq.com",
-	//	"mx3.qq.com",
-	//	"pop.qq.com",
-	//	"smtp.qq.com",
-	//	"mail.qq.com",
-	//}
-	//
-	//fmt.Println(e.domainMatch("", dnsNames))
-	//fmt.Println(e.domainMatch("xjiangwei.cn", dnsNames))
-	//fmt.Println(e.domainMatch("qq.com", dnsNames))
-	//fmt.Println(e.domainMatch("test.aaa.mail.qq.com", dnsNames))
-	//fmt.Println(e.domainMatch("smtp.qq.com", dnsNames))
-	//fmt.Println(e.domainMatch("pop.qq.com", dnsNames))
-	//fmt.Println(e.domainMatch("test.mail.qq.com", dnsNames))
-
-}
-
 func Test_buildUser(t *testing.T) {
 	u := buildUser("Jinnrry N <jiangwei1995910@gmail.com>")
 	if u.EmailAddress != "jiangwei1995910@gmail.com" {
@@ -126,8 +65,6 @@ func TestEmailBuidlers(t *testing.T) {
 }
 
 func TestEmail_builder(t *testing.T) {
-	testInit()
-
 	e := Email{
 		From:    buildUser("i@test.com"),
 		To:      buildUsers([]string{"to@test.com"}),

+ 10 - 0
server/hooks/base.go

@@ -151,6 +151,8 @@ func NewHookSender(socketPath string, name string, serverVersion string) *HookSe
 	}
 }
 
+var processList []*os.Process
+
 // Init 注册hook对象
 func Init(serverVersion string) {
 
@@ -184,6 +186,7 @@ func Init(serverVersion string) {
 				return nil
 			}
 			fmt.Printf("[%s] Plugin Start! PID:%d", info.Name(), p.Pid)
+			processList = append(processList, p)
 
 			pluginNo++
 
@@ -214,3 +217,10 @@ func Init(serverVersion string) {
 	})
 
 }
+
+func Stop() {
+	log.Info("Plugin Stop")
+	for _, process := range processList {
+		process.Kill()
+	}
+}

+ 0 - 21
server/hooks/telegram_push/telegram_push_test.go

@@ -1,21 +0,0 @@
-package main
-
-import (
-	"pmail/config"
-	"pmail/dto/parsemail"
-	"testing"
-)
-
-func testInit() {
-
-	config.Init()
-
-}
-func TestWeChatPushHook_ReceiveParseAfter(t *testing.T) {
-	testInit()
-
-	w := NewTelegramPushHook()
-	w.ReceiveParseAfter(&parsemail.Email{Subject: "标题", Text: []byte("文本内容"), From: &parsemail.User{
-		EmailAddress: "hello@gmail.com",
-	}})
-}

+ 0 - 19
server/hooks/web_push/wechat_push_test.go

@@ -1,19 +0,0 @@
-package main
-
-import (
-	"pmail/config"
-	"pmail/dto/parsemail"
-	"testing"
-)
-
-func testInit() {
-
-	config.Init()
-
-}
-func TestWebPushHook_ReceiveParseAfter(t *testing.T) {
-	testInit()
-
-	w := NewWebPushHook()
-	w.ReceiveParseAfter(nil, &parsemail.Email{Subject: "标题", Text: []byte("文本内容")})
-}

+ 1 - 1
server/hooks/wechat_push/wechat_push.go

@@ -131,7 +131,7 @@ func NewWechatPushHook() *WeChatPushHook {
 	var cfgData []byte
 	var err error
 
-	cfgData, err = os.ReadFile("./config/config.json")
+	cfgData, err = os.ReadFile("../../config/config.json")
 	if err != nil {
 		panic(err)
 	}

+ 0 - 19
server/hooks/wechat_push/wechat_push_test.go

@@ -1,19 +0,0 @@
-package main
-
-import (
-	"pmail/config"
-	"pmail/dto/parsemail"
-	"testing"
-)
-
-func testInit() {
-
-	config.Init()
-
-}
-func TestWeChatPushHook_ReceiveParseAfter(t *testing.T) {
-	testInit()
-
-	w := NewWechatPushHook()
-	w.ReceiveParseAfter(nil, &parsemail.Email{Subject: "标题", Text: []byte("文本内容")})
-}

+ 2 - 1
server/http_server/http_server.go

@@ -1,6 +1,7 @@
 package http_server
 
 import (
+	"errors"
 	"fmt"
 	log "github.com/sirupsen/logrus"
 	"io/fs"
@@ -74,7 +75,7 @@ func HttpStart() {
 	}
 
 	err := httpServer.ListenAndServe()
-	if err != nil {
+	if err != nil && !errors.Is(err, http.ErrServerClosed) {
 		panic(err)
 	}
 }

+ 18 - 0
server/http_server/setup_server.go

@@ -4,11 +4,14 @@ import (
 	"flag"
 	"fmt"
 	log "github.com/sirupsen/logrus"
+	"github.com/spf13/cast"
 	"io/fs"
 	"net/http"
+	"os"
 	"pmail/config"
 	"pmail/controllers"
 	"pmail/utils/ip"
+	"strings"
 	"time"
 )
 
@@ -29,6 +32,20 @@ func SetupStart() {
 	HttpPort := 80
 	flag.IntVar(&HttpPort, "p", 80, "初始化阶段Http服务端口")
 	flag.Parse()
+
+	if HttpPort == 80 {
+		envs := os.Environ()
+		for _, env := range envs {
+			if strings.HasPrefix(env, "setup_port=") {
+				HttpPort = cast.ToInt(strings.TrimSpace(strings.ReplaceAll(env, "setup_port=", "")))
+			}
+		}
+	}
+
+	if HttpPort <= 0 || HttpPort > 65535 {
+		HttpPort = 80
+	}
+
 	config.Instance.SetSetupPort(HttpPort)
 	log.Infof("HttpServer Start On Port :%d", HttpPort)
 	if HttpPort == 80 {
@@ -51,6 +68,7 @@ func SetupStart() {
 
 func SetupStop() {
 	err := setupServer.Close()
+	log.Infof("Setup End!")
 	if err != nil {
 		panic(err)
 	}

+ 2 - 2
server/main.go

@@ -92,6 +92,6 @@ func main() {
 	// 核心服务启动
 	res_init.Init(version)
 
-	s := make(chan bool)
-	<-s
+	log.Warnf("Server Stoped \n")
+
 }

+ 405 - 0
server/main_test.go

@@ -0,0 +1,405 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"github.com/spf13/cast"
+	"io"
+	"net"
+	"net/http"
+	"net/http/cookiejar"
+	"os"
+	"pmail/db"
+	"pmail/dto/response"
+	"pmail/models"
+	"pmail/services/setup"
+	"pmail/signal"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+)
+
+var httpClient *http.Client
+
+const TestPort = 17888
+
+var TestHost string = "http://127.0.0.1:" + cast.ToString(TestPort)
+
+func TestMain(m *testing.M) {
+	cookeieJar, err := cookiejar.New(nil)
+	if err != nil {
+		panic(err)
+	}
+
+	httpClient = &http.Client{Jar: cookeieJar, Timeout: 5 * time.Second}
+	os.Remove("config/config.json")
+	os.Remove("config/pmail_temp.db")
+	go func() {
+		main()
+	}()
+	time.Sleep(3 * time.Second)
+
+	m.Run()
+
+	signal.StopChan <- true
+	time.Sleep(3 * time.Second)
+}
+
+func TestMaster(t *testing.T) {
+	t.Run("TestPort", testPort)
+	t.Run("testDataBaseSet", testDataBaseSet)
+	t.Run("testPwdSet", testPwdSet)
+	t.Run("testDomainSet", testDomainSet)
+	t.Run("testDNSSet", testDNSSet)
+	cfg, err := setup.ReadConfig()
+	if err != nil {
+		t.Fatal(err)
+	}
+	cfg.HttpsEnabled = 2
+	cfg.HttpPort = TestPort
+	err = setup.WriteConfig(cfg)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Run("testSSLSet", testSSLSet)
+	time.Sleep(3 * time.Second)
+	t.Run("testLogin", testLogin)
+	t.Run("testSendEmail", testSendEmail)
+	time.Sleep(3 * time.Second)
+	t.Run("testEmailList", testEmailList)
+	t.Run("testDelEmail", testDelEmail)
+}
+
+func testPort(t *testing.T) {
+	if !portCheck(TestPort) {
+		t.Error("port check failed")
+	}
+	t.Log("port check passed")
+}
+
+func testDataBaseSet(t *testing.T) {
+
+	// 获取配置
+	ret, err := http.Post(TestHost+"/api/setup", "application/json", strings.NewReader("{\"action\":\"get\",\"step\":\"database\"}"))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err := readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get Database Config Api Error!")
+	}
+	// 设置配置
+	ret, err = http.Post(TestHost+"/api/setup", "application/json", strings.NewReader(`
+{"action":"set","step":"database","db_type":"sqlite","db_dsn":"./config/pmail_temp.db"}
+`))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err = readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get Database Config Api Error!")
+	}
+
+	// 获取配置
+	ret, err = http.Post(TestHost+"/api/setup", "application/json", strings.NewReader("{\"action\":\"get\",\"step\":\"database\"}"))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err = readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get Database Config Api Error!")
+	}
+	dt := data.Data.(map[string]interface{})
+	if cast.ToString(dt["db_dsn"]) != "./config/pmail_temp.db" {
+		t.Error("Check Database Config Api Error!")
+	}
+
+	t.Log("Database Config Api Success!")
+}
+
+func testPwdSet(t *testing.T) {
+
+	// 获取配置
+	ret, err := http.Post(TestHost+"/api/setup", "application/json", strings.NewReader("{\"action\":\"get\",\"step\":\"password\"}"))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err := readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get Password Config Api Error!")
+	}
+	// 设置配置
+	ret, err = http.Post(TestHost+"/api/setup", "application/json", strings.NewReader(`
+{"action":"set","step":"password","account":"testCase","password":"testCase"}
+`))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err = readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Set Password Config Api Error!")
+	}
+
+	// 获取配置
+	ret, err = http.Post(TestHost+"/api/setup", "application/json", strings.NewReader("{\"action\":\"get\",\"step\":\"password\"}"))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err = readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get Password Config Api Error!")
+	}
+
+	if cast.ToString(data.Data) != "testCase" {
+		t.Error("Check Password Config Api Error!")
+	}
+
+	t.Log("Password Config Api Success!")
+}
+
+func testDomainSet(t *testing.T) {
+	// 获取配置
+	ret, err := http.Post(TestHost+"/api/setup", "application/json", strings.NewReader("{\"action\":\"get\",\"step\":\"domain\"}"))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err := readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get domain Config Api Error!")
+	}
+	// 设置配置
+	ret, err = http.Post(TestHost+"/api/setup", "application/json", strings.NewReader(`
+{"action":"set","step":"domain","smtp_domain":"test.domain","web_domain":"mail.test.domain"}
+`))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err = readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Set domain Config Api Error!")
+	}
+
+	// 获取配置
+	ret, err = http.Post(TestHost+"/api/setup", "application/json", strings.NewReader("{\"action\":\"get\",\"step\":\"domain\"}"))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err = readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get Password Config Api Error!")
+	}
+
+	dt := data.Data.(map[string]interface{})
+
+	if cast.ToString(dt["smtp_domain"]) != "test.domain" {
+		t.Error("Check domain Config Api Error!")
+	}
+	if cast.ToString(dt["web_domain"]) != "mail.test.domain" {
+		t.Error("Check domain Config Api Error!")
+	}
+	t.Log("domain Config Api Success!")
+}
+
+func testDNSSet(t *testing.T) {
+	// 获取配置
+	ret, err := http.Post(TestHost+"/api/setup", "application/json", strings.NewReader("{\"action\":\"get\",\"step\":\"dns\"}"))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err := readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get domain Config Api Error!")
+	}
+}
+
+func testSSLSet(t *testing.T) {
+	// 获取配置
+	ret, err := http.Post(TestHost+"/api/setup", "application/json", strings.NewReader("{\"action\":\"get\",\"step\":\"ssl\"}"))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err := readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get domain Config Api Error!")
+	}
+	// 设置配置
+	ret, err = http.Post(TestHost+"/api/setup", "application/json", strings.NewReader(`
+{"action":"set","step":"ssl","ssl_type":"1","key_path":"./config/ssl/private.key","crt_path":"./config/ssl/public.crt"}
+`))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err = readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Set domain Config Api Error!")
+	}
+
+	t.Log("domain Config Api Success!")
+}
+
+func testLogin(t *testing.T) {
+	ret, err := httpClient.Post(TestHost+"/api/login", "application/json", strings.NewReader("{\"account\":\"testCase\",\"password\":\"testCase\"}"))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err := readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get domain Config Api Error!")
+	}
+}
+
+func testSendEmail(t *testing.T) {
+	ret, err := httpClient.Post(TestHost+"/api/email/send", "application/json", strings.NewReader(`
+		{
+    "from": {
+        "name": "i",
+        "email": "i@test.domain"
+    },
+    "to": [
+        {
+            "name": "y",
+            "email": "y@test.domain"
+        }
+    ],
+    "cc": [
+        
+    ],
+    "subject": "Title",
+    "text": "text",
+    "html": "<div>text</div>"
+}
+
+`))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err := readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Send Email Api Error!")
+	}
+}
+
+func testEmailList(t *testing.T) {
+	ret, err := httpClient.Post(TestHost+"/api/email/list", "application/json", strings.NewReader(`{}`))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err := readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get Email List Api Error!")
+	}
+	dt := data.Data.(map[string]interface{})
+	if len(dt["list"].([]interface{})) == 0 {
+		t.Error("Email List Is Empty!")
+	}
+}
+
+func testDelEmail(t *testing.T) {
+	ret, err := httpClient.Post(TestHost+"/api/email/list", "application/json", strings.NewReader(`{}`))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err := readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Get Email List Api Error!")
+	}
+	dt := data.Data.(map[string]interface{})
+	if len(dt["list"].([]interface{})) == 0 {
+		t.Error("Email List Is Empty!")
+	}
+	lst := dt["list"].([]interface{})
+	item := lst[0].(map[string]interface{})
+	id := cast.ToInt(item["id"])
+
+	ret, err = httpClient.Post(TestHost+"/api/email/del", "application/json", strings.NewReader(fmt.Sprintf(`{
+	"ids":[%d]	
+}`, id)))
+	if err != nil {
+		t.Error(err)
+	}
+	data, err = readResponse(ret.Body)
+	if err != nil {
+		t.Error(err)
+	}
+	if data.ErrorNo != 0 {
+		t.Error("Email Delete Api Error!")
+	}
+	var mail models.Email
+	db.Instance.Where("id = ?", id).Get(&mail)
+	if mail.Status != 3 {
+		t.Error("Email Delete Api Error!")
+	}
+
+}
+
+// portCheck 检查端口是占用
+func portCheck(port int) bool {
+	l, err := net.Listen("tcp", fmt.Sprintf(":%s", strconv.Itoa(port)))
+	if err != nil {
+		return true
+	}
+	defer l.Close()
+	return false
+}
+
+func readResponse(r io.Reader) (*response.Response, error) {
+	data, err := io.ReadAll(r)
+	if err != nil {
+		return nil, err
+	}
+	ret := &response.Response{}
+	err = json.Unmarshal(data, ret)
+	if err != nil {
+		return nil, err
+	}
+	return ret, nil
+}

+ 0 - 30
server/models/base.go

@@ -1,30 +0,0 @@
-package models
-
-import "pmail/db"
-
-func SyncTables() {
-	err := db.Instance.Sync2(&User{})
-	if err != nil {
-		panic(err)
-	}
-	err = db.Instance.Sync2(&Email{})
-	if err != nil {
-		panic(err)
-	}
-	err = db.Instance.Sync2(&Group{})
-	if err != nil {
-		panic(err)
-	}
-	err = db.Instance.Sync2(&Rule{})
-	if err != nil {
-		panic(err)
-	}
-	err = db.Instance.Sync2(&UserAuth{})
-	if err != nil {
-		panic(err)
-	}
-	err = db.Instance.Sync2(&Sessions{})
-	if err != nil {
-		panic(err)
-	}
-}

+ 2 - 2
server/models/email.go

@@ -8,7 +8,7 @@ import (
 )
 
 type Email struct {
-	Id           int            `xorm:"id pk unsigned int autoincr notnull default(0)" json:"id"`
+	Id           int            `xorm:"id pk unsigned int autoincr notnull" json:"id"`
 	Type         int8           `xorm:"type tinyint(4) notnull default(0) comment('邮件类型,0:收到的邮件,1:发送的邮件')" json:"type"`
 	GroupId      int            `xorm:"group_id int notnull default(0) comment('分组id')'" json:"group_id"`
 	Subject      string         `xorm:"subject varchar(1000) notnull default('') comment('邮件标题')" json:"subject"`
@@ -34,7 +34,7 @@ type Email struct {
 	CreateTime   time.Time      `xorm:"create_time created" json:"create_time"`
 }
 
-func (p Email) TableName() string {
+func (d Email) TableName() string {
 	return "email"
 }
 

+ 0 - 24
server/models/rule.go

@@ -1,11 +1,5 @@
 package models
 
-import (
-	"pmail/db"
-	"pmail/utils/context"
-	"pmail/utils/errors"
-)
-
 type Rule struct {
 	Id     int    `xorm:"id int unsigned not null pk autoincr" json:"id"`
 	UserId int    `xorm:"user_id notnull default(0) comment('用户id')" json:"user_id"`
@@ -19,21 +13,3 @@ type Rule struct {
 func (p *Rule) TableName() string {
 	return "rule"
 }
-
-func (p *Rule) Save(ctx *context.Context) error {
-
-	if p.Id > 0 {
-		_, err := db.Instance.Exec(db.WithContext(ctx, "update rule set name=? ,value = ? ,action = ?,params = ?,sort = ? where id = ?"), p.Name, p.Value, p.Action, p.Params, p.Sort, p.Id)
-		if err != nil {
-			return errors.Wrap(err)
-		}
-		return nil
-	} else {
-		_, err := db.Instance.Exec(db.WithContext(ctx, "insert into rule (name,value,user_id,action,params,sort) values (?,?,?,?,?,?)"), p.Name, p.Value, ctx.UserID, p.Action, p.Params, p.Sort)
-		if err != nil {
-			return errors.Wrap(err)
-		}
-		return nil
-	}
-
-}

+ 0 - 53
server/pop3_server/action_test.go

@@ -1,53 +0,0 @@
-package pop3_server
-
-import (
-	"fmt"
-	"github.com/Jinnrry/gopop"
-	log "github.com/sirupsen/logrus"
-	"os"
-	"pmail/config"
-	"pmail/db"
-	parsemail2 "pmail/dto/parsemail"
-	"pmail/hooks"
-	"pmail/session"
-	"pmail/utils/context"
-	"testing"
-	"time"
-)
-
-func testInit() {
-	// 设置日志格式为json格式
-	//log.SetFormatter(&log.JSONFormatter{})
-
-	log.SetReportCaller(true)
-	log.SetFormatter(&log.TextFormatter{
-		//以下设置只是为了使输出更美观
-		DisableColors:   true,
-		TimestampFormat: "2006-01-02 15:03:04",
-	})
-
-	// 设置将日志输出到标准输出(默认的输出为stderr,标准错误)
-	// 日志消息输出可以是任意的io.writer类型
-	log.SetOutput(os.Stdout)
-
-	// 设置日志级别为warn以上
-	log.SetLevel(log.TraceLevel)
-
-	var cst, _ = time.LoadLocation("Asia/Shanghai")
-	time.Local = cst
-
-	config.Init()
-	parsemail2.Init()
-	db.Init()
-	session.Init()
-	hooks.Init("dev")
-}
-
-func Test_action_Stat(t *testing.T) {
-	testInit()
-	act := action{}
-	v1, v2, v3 := act.Stat(&gopop.Session{
-		Ctx: &context.Context{},
-	})
-	fmt.Println(v1, v2, v3)
-}

+ 18 - 8
server/res_init/init.go

@@ -9,7 +9,6 @@ import (
 	"pmail/dto/parsemail"
 	"pmail/hooks"
 	"pmail/http_server"
-	"pmail/models"
 	"pmail/pop3_server"
 	"pmail/services/setup/ssl"
 	"pmail/session"
@@ -37,7 +36,6 @@ func Init(serverVersion string) {
 		if err != nil {
 			panic(err)
 		}
-		models.SyncTables()
 		session.Init()
 		hooks.Init(serverVersion)
 		// smtp server start
@@ -53,12 +51,24 @@ func Init(serverVersion string) {
 		configStr, _ := json.Marshal(config.Instance)
 		log.Warnf("Config File Info:  %s", configStr)
 
-		<-signal.RestartChan
-		log.Infof("Server Restart!")
-		smtp_server.Stop()
-		http_server.HttpsStop()
-		http_server.HttpStop()
-		pop3_server.Stop()
+		select {
+		case <-signal.RestartChan:
+			log.Infof("Server Restart!")
+			smtp_server.Stop()
+			http_server.HttpsStop()
+			http_server.HttpStop()
+			pop3_server.Stop()
+			hooks.Stop()
+		case <-signal.StopChan:
+			log.Infof("Server Stop!")
+			smtp_server.Stop()
+			http_server.HttpsStop()
+			http_server.HttpStop()
+			pop3_server.Stop()
+			hooks.Stop()
+			return
+		}
+
 	}
 
 }

+ 0 - 7
server/services/auth/auth_test.go

@@ -1,7 +0,0 @@
-package auth
-
-import "testing"
-
-func TestDkimGen(t *testing.T) {
-	DkimGen()
-}

+ 5 - 4
server/services/del_email/del_email.go

@@ -1,19 +1,18 @@
 package del_email
 
 import (
-	"fmt"
 	"pmail/db"
 	"pmail/models"
 	"pmail/services/auth"
-	"pmail/utils/array"
 	"pmail/utils/context"
 	"pmail/utils/errors"
+	"xorm.io/builder"
 )
 
 func DelEmail(ctx *context.Context, ids []int) error {
 	var emails []*models.Email
 
-	err := db.Instance.ID(ids).Find(&emails)
+	err := db.Instance.Table("email").Where(builder.In("id", ids)).Find(&emails)
 	if err != nil {
 		return errors.Wrap(err)
 	}
@@ -23,9 +22,11 @@ func DelEmail(ctx *context.Context, ids []int) error {
 		if !hasAuth {
 			return errors.New("No Auth!")
 		}
+		email.Status = 3
 	}
 
-	_, err = db.Instance.Exec(db.WithContext(ctx, fmt.Sprintf("update email set status = 3 where id in (%s)", array.Join(ids, ","))))
+	_, err = db.Instance.Table("email").Where(builder.In("id", ids)).Cols("status").Update(map[string]interface{}{"status": 3})
+
 	if err != nil {
 		return errors.Wrap(err)
 	}

+ 0 - 18
server/services/rule/match/regex_match_test.go

@@ -1,18 +0,0 @@
-package match
-
-import (
-	"pmail/models"
-	"testing"
-)
-
-func TestRegexMatch_Match(t *testing.T) {
-	r := NewRegexMatch("Subject", "\\d+")
-
-	ret := r.Match(nil, &models.Email{
-		Subject: "111",
-	})
-
-	if !ret {
-		t.Errorf("失败")
-	}
-}

+ 0 - 1
server/services/setup/db.go

@@ -85,7 +85,6 @@ func SetDatabaseSettings(ctx *context.Context, dbType, dbDSN string) error {
 	if err != nil {
 		return errors.Wrap(err)
 	}
-	models.SyncTables()
 	return nil
 }
 

+ 0 - 10
server/services/setup/db_test.go

@@ -1,10 +0,0 @@
-package setup
-
-import (
-	"testing"
-)
-
-func TestSetAdminPassword(t *testing.T) {
-
-	SetAdminPassword(nil, "admin", "admin")
-}

+ 0 - 7
server/services/setup/dns_test.go

@@ -1,7 +0,0 @@
-package setup
-
-import "testing"
-
-func TestGetIp(t *testing.T) {
-	getIp()
-}

+ 6 - 1
server/services/setup/ssl/ssl.go

@@ -50,7 +50,7 @@ func GetSSL() string {
 	return cfg.SSLType
 }
 
-func SetSSL(sslType string) error {
+func SetSSL(sslType, priKey, crtKey string) error {
 	cfg, err := setup.ReadConfig()
 	if err != nil {
 		panic(err)
@@ -61,6 +61,11 @@ func SetSSL(sslType string) error {
 		return errors.New("SSL Type Error!")
 	}
 
+	if cfg.SSLType == config.SSLTypeUser {
+		cfg.SSLPrivateKeyPath = priKey
+		cfg.SSLPublicKeyPath = crtKey
+	}
+
 	err = setup.WriteConfig(cfg)
 	if err != nil {
 		return errors.Wrap(err)

+ 0 - 17
server/services/setup/ssl/ssl_test.go

@@ -1,17 +0,0 @@
-package ssl
-
-import (
-	"fmt"
-	"testing"
-)
-
-func TestGenSSL(t *testing.T) {
-	err := GenSSL(false)
-	fmt.Println(err)
-}
-
-func TestGetSSLCrtInfo(t *testing.T) {
-	days, tm, err := CheckSSLCrtInfo()
-
-	fmt.Println(days, tm, err)
-}

+ 6 - 0
server/signal/signal.go

@@ -1,4 +1,10 @@
 package signal
 
+// InitChan 控制初始化流程结束
 var InitChan = make(chan bool)
+
+// RestartChan 控制程序重启
 var RestartChan = make(chan bool)
+
+// StopChan 控制程序结束
+var StopChan = make(chan bool)

+ 11 - 72
server/smtp_server/read_content_test.go

@@ -3,11 +3,9 @@ package smtp_server
 import (
 	"bytes"
 	log "github.com/sirupsen/logrus"
-	"io/fs"
 	"net"
 	"net/netip"
 	"os"
-	"path/filepath"
 	"pmail/config"
 	"pmail/db"
 	parsemail2 "pmail/dto/parsemail"
@@ -34,62 +32,22 @@ func testInit() {
 	log.SetOutput(os.Stdout)
 
 	// 设置日志级别为warn以上
-	log.SetLevel(log.TraceLevel)
+	log.SetLevel(log.ErrorLevel)
 
 	var cst, _ = time.LoadLocation("Asia/Shanghai")
 	time.Local = cst
 
 	config.Init()
+	config.Instance.DkimPrivateKeyPath = "../config/dkim/dkim.priv"
+	config.Instance.DbType = config.DBTypeSQLite
+	config.Instance.DbDSN = "../config/pmail_temp.db"
+
 	parsemail2.Init()
 	db.Init()
 	session.Init()
 	hooks.Init("dev")
 }
 
-func TestNuisanace(t *testing.T) {
-	testInit()
-
-	s := Session{
-		RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
-		Ctx: &context.Context{
-			UserID:      1,
-			UserName:    "a",
-			UserAccount: "a",
-		},
-	}
-
-	data, _ := os.ReadFile("../docs/nuisance/demo.txt")
-	s.Data(bytes.NewReader(data))
-
-}
-
-func TestSession_Data(t *testing.T) {
-	testInit()
-	s := Session{
-		RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
-	}
-
-	filepath.WalkDir("docs", func(path string, d fs.DirEntry, err error) error {
-		if !d.IsDir() {
-			data, _ := os.ReadFile(path)
-			s.Data(bytes.NewReader(data))
-		}
-		return nil
-	})
-
-}
-
-func TestSession_DataGmail(t *testing.T) {
-	testInit()
-	s := Session{
-		RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
-	}
-
-	data, _ := os.ReadFile("docs/gmail/带附件带图片.txt")
-	s.Data(bytes.NewReader(data))
-
-}
-
 func TestPmailEmail(t *testing.T) {
 	testInit()
 	emailData := `DKIM-Signature: a=rsa-sha256; bh=x7Rh+N2y2K9exccEAyKCTAGDgYKfnLZpMWc25ug5Ny4=;
@@ -134,6 +92,7 @@ Content-Type: text/html
 			UserName:    "",
 			UserAccount: "",
 		},
+		To: []string{"ok@jinnrry.com"},
 	}
 
 	s.Data(bytes.NewReader([]byte(emailData)))
@@ -293,11 +252,7 @@ Content-Type: text/html
 
 	s := Session{
 		RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
-		Ctx: &context.Context{
-			UserID:      1,
-			UserName:    "a",
-			UserAccount: "a",
-		},
+		Ctx:           &context.Context{},
 	}
 
 	s.Data(bytes.NewReader([]byte(deleteEmail)))
@@ -348,11 +303,7 @@ Content-Type: text/html
 
 	s := Session{
 		RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
-		Ctx: &context.Context{
-			UserID:      1,
-			UserName:    "a",
-			UserAccount: "a",
-		},
+		Ctx:           &context.Context{},
 	}
 
 	s.Data(bytes.NewReader([]byte(readEmail)))
@@ -444,11 +395,7 @@ Pui/meaYr+S4gOWwgeadpeiHqlJlbGF4RHJhbWHnmoTmoKHpqozpgq7ku7Ys55So5LqO5qCh6aqM
 --6edc2ef285d93010a080caccc858c67b--`
 	s := Session{
 		RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
-		Ctx: &context.Context{
-			UserID:      1,
-			UserName:    "a",
-			UserAccount: "a",
-		},
+		Ctx:           &context.Context{},
 	}
 
 	s.Data(bytes.NewReader([]byte(emailData)))
@@ -496,11 +443,7 @@ Content-Type: text/html
 
 	s := Session{
 		RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
-		Ctx: &context.Context{
-			UserID:      1,
-			UserName:    "a",
-			UserAccount: "a",
-		},
+		Ctx:           &context.Context{},
 	}
 
 	s.Data(bytes.NewReader([]byte(moveEmail)))
@@ -534,11 +477,7 @@ PGRpdj7ov5nph4zmmK/lhoXlrrk8L2Rpdj48ZGl2PjwhLS1lbXB0eXNpZ24tLT48L2Rpdj4=
 
 	s := Session{
 		RemoteAddress: net.TCPAddrFromAddrPort(netip.AddrPortFrom(netip.AddrFrom4([4]byte{}), 25)),
-		Ctx: &context.Context{
-			UserID:      1,
-			UserName:    "a",
-			UserAccount: "a",
-		},
+		Ctx:           &context.Context{},
 	}
 
 	s.Data(bytes.NewReader([]byte(data)))

+ 5 - 0
server/utils/consts/consts.go

@@ -0,0 +1,5 @@
+package consts
+
+const (
+	TEST_DOMAIN = "test.domain"
+)

+ 6 - 6
server/utils/context/context.go

@@ -9,12 +9,12 @@ const (
 )
 
 type Context struct {
-	context.Context
-	UserID      int
-	UserAccount string
-	UserName    string
-	Values      map[string]any
-	Lang        string
+	context.Context `json:"-"`
+	UserID          int
+	UserAccount     string
+	UserName        string
+	Values          map[string]any
+	Lang            string
 }
 
 func (c *Context) SetValue(key string, value any) {

+ 0 - 29
server/utils/errors/error_test.go

@@ -1,29 +0,0 @@
-package errors
-
-import (
-	"fmt"
-	"testing"
-)
-
-func TestNew(t *testing.T) {
-	err := New("err")
-	fmt.Println(err)
-}
-
-func TestWarp(t *testing.T) {
-	err := New("err1")
-	err = Wrap(err)
-	err = Wrap(err)
-	err = Wrap(err)
-	err = Wrap(err)
-	fmt.Println(err)
-}
-
-func TestWarpWithMsg(t *testing.T) {
-	err := New("err1")
-	err = Wrap(err)
-	err = Wrap(err)
-	err = Wrap(err)
-	err = WrapWithMsg(err, "last")
-	fmt.Println(err)
-}

+ 0 - 10
server/utils/password/encode_test.go

@@ -1,10 +0,0 @@
-package password
-
-import (
-	"fmt"
-	"testing"
-)
-
-func TestEncode(t *testing.T) {
-	fmt.Println(Encode("admin"))
-}

+ 48 - 29
server/utils/send/send.go

@@ -9,6 +9,7 @@ import (
 	"pmail/dto/parsemail"
 	"pmail/utils/array"
 	"pmail/utils/async"
+	"pmail/utils/consts"
 	"pmail/utils/context"
 	"pmail/utils/smtp"
 	"strings"
@@ -36,22 +37,31 @@ func Forward(ctx *context.Context, e *parsemail.Email, forwardAddress string) er
 	for _, s := range to {
 		args := strings.Split(s.EmailAddress, "@")
 		if len(args) == 2 {
-			//查询dns mx记录
-			mxInfo, err := net.LookupMX(args[1])
-			address := mxDomain{
-				domain: "smtp." + args[1],
-				mxHost: "smtp." + args[1],
-			}
-			if err != nil {
-				log.WithContext(ctx).Errorf(s.EmailAddress, "域名mx记录查询失败")
-			}
-			if len(mxInfo) > 0 {
-				address = mxDomain{
-					domain: args[1],
-					mxHost: mxInfo[0].Host,
+			if args[1] == consts.TEST_DOMAIN {
+				// 测试使用
+				address := mxDomain{
+					domain: "localhost",
+					mxHost: "127.0.0.1",
+				}
+				toByDomain[address] = append(toByDomain[address], s)
+			} else {
+				//查询dns mx记录
+				mxInfo, err := net.LookupMX(args[1])
+				address := mxDomain{
+					domain: "smtp." + args[1],
+					mxHost: "smtp." + args[1],
 				}
+				if err != nil {
+					log.WithContext(ctx).Errorf(s.EmailAddress, "域名mx记录查询失败")
+				}
+				if len(mxInfo) > 0 {
+					address = mxDomain{
+						domain: args[1],
+						mxHost: mxInfo[0].Host,
+					}
+				}
+				toByDomain[address] = append(toByDomain[address], s)
 			}
-			toByDomain[address] = append(toByDomain[address], s)
 		} else {
 			log.WithContext(ctx).Errorf("邮箱地址解析错误! %s", s)
 			continue
@@ -119,22 +129,31 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
 	for _, s := range to {
 		args := strings.Split(s.EmailAddress, "@")
 		if len(args) == 2 {
-			//查询dns mx记录
-			mxInfo, err := net.LookupMX(args[1])
-			address := mxDomain{
-				domain: "smtp." + args[1],
-				mxHost: "smtp." + args[1],
-			}
-			if err != nil {
-				log.WithContext(ctx).Errorf(s.EmailAddress, "域名mx记录查询失败")
-			}
-			if len(mxInfo) > 0 {
-				address = mxDomain{
-					domain: args[1],
-					mxHost: mxInfo[0].Host,
+			if args[1] == consts.TEST_DOMAIN {
+				// 测试使用
+				address := mxDomain{
+					domain: "localhost",
+					mxHost: "127.0.0.1",
+				}
+				toByDomain[address] = append(toByDomain[address], s)
+			} else {
+				//查询dns mx记录
+				mxInfo, err := net.LookupMX(args[1])
+				address := mxDomain{
+					domain: "smtp." + args[1],
+					mxHost: "smtp." + args[1],
 				}
+				if err != nil {
+					log.WithContext(ctx).Errorf(s.EmailAddress, "域名mx记录查询失败")
+				}
+				if len(mxInfo) > 0 {
+					address = mxDomain{
+						domain: args[1],
+						mxHost: mxInfo[0].Host,
+					}
+				}
+				toByDomain[address] = append(toByDomain[address], s)
 			}
-			toByDomain[address] = append(toByDomain[address], s)
 		} else {
 			log.WithContext(ctx).Errorf("邮箱地址解析错误! %s", s)
 			continue
@@ -190,7 +209,7 @@ func Send(ctx *context.Context, e *parsemail.Email) (error, map[string]error) {
 	errMap.Range(func(key, value any) bool {
 		if value != nil {
 			orgMap[key.(string)] = value.(error)
-		}else {
+		} else {
 			orgMap[key.(string)] = nil
 		}
 

+ 0 - 92
server/utils/send/send_test.go

@@ -1,92 +0,0 @@
-package send
-
-import (
-	"fmt"
-	log "github.com/sirupsen/logrus"
-	"os"
-	"pmail/config"
-	"pmail/dto/parsemail"
-	"testing"
-	"time"
-)
-
-func testInit() {
-	// 设置日志格式为json格式
-	//log.SetFormatter(&log.JSONFormatter{})
-
-	log.SetReportCaller(true)
-	log.SetFormatter(&log.TextFormatter{
-		//以下设置只是为了使输出更美观
-		DisableColors:   true,
-		TimestampFormat: "2006-01-02 15:03:04",
-	})
-
-	// 设置将日志输出到标准输出(默认的输出为stderr,标准错误)
-	// 日志消息输出可以是任意的io.writer类型
-	log.SetOutput(os.Stdout)
-
-	// 设置日志级别为warn以上
-	log.SetLevel(log.TraceLevel)
-
-	var cst, _ = time.LoadLocation("Asia/Shanghai")
-	time.Local = cst
-
-	config.Init()
-	parsemail.Init()
-}
-func TestSend(t *testing.T) {
-	testInit()
-	e := &parsemail.Email{
-		From: &parsemail.User{
-			Name:         "发送人",
-			EmailAddress: "j@jinnrry.com",
-		},
-		To: []*parsemail.User{
-			{"ok@jinnrry.com", "名"},
-		},
-		Subject: "插件测试",
-		Text:    []byte("这是Text"),
-		HTML:    []byte("<div>这是Html</div>"),
-	}
-	Send(nil, e)
-}
-
-func TestSendSohu(t *testing.T) {
-	testInit()
-	e := &parsemail.Email{
-		From: &parsemail.User{
-			Name:         "发送人",
-			EmailAddress: "j@jinnrry.com",
-		},
-		To: []*parsemail.User{
-			{"jinnrry@sohu.com", "名"},
-		},
-		Subject: "插件测试",
-		Text:    []byte("这是Text"),
-		HTML:    []byte("<div>这是Html</div>"),
-	}
-	Send(nil, e)
-}
-
-func TestSendTom(t *testing.T) {
-	testInit()
-	e := &parsemail.Email{
-		From: &parsemail.User{
-			Name:         "发送人",
-			EmailAddress: "j@jinnrry.com",
-		},
-		To: []*parsemail.User{
-			{"tom@tom.com", "名"},
-		},
-		Subject: "插件测试",
-		Text:    []byte("这是Text"),
-		HTML:    []byte("<div>这是Html</div>"),
-	}
-	Send(nil, e)
-}
-
-func Test_domainMatch(t *testing.T) {
-	domain := domainMatch("qq.com", nil)
-
-	fmt.Println(domain)
-}