1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-03-21 21:47:19 +02:00

Merge branch 'release/v1.15.1'

This commit is contained in:
Ralph Slooten 2024-03-31 00:11:48 +13:00
commit ebe9195075
22 changed files with 164 additions and 172 deletions

View File

@ -2,6 +2,16 @@
Notable changes to Mailpit will be documented in this file. Notable changes to Mailpit will be documented in this file.
## [v1.15.1]
### Chore
- Code cleanup, remove redundant functionality
- Add labels to Docker image ([#267](https://github.com/axllent/mailpit/issues/267))
### Feature
- Add readyz subcommand for Docker healthcheck ([#270](https://github.com/axllent/mailpit/issues/270))
## [v1.15.0] ## [v1.15.0]
### Chore ### Chore

View File

@ -12,10 +12,19 @@ CGO_ENABLED=0 go build -ldflags "-s -w -X github.com/axllent/mailpit/config.Vers
FROM alpine:latest FROM alpine:latest
LABEL org.opencontainers.image.title="Mailpit" \
org.opencontainers.image.description="An email and SMTP testing tool with API for developers" \
org.opencontainers.image.source="https://github.com/axllent/mailpit" \
org.opencontainers.image.url="https://mailpit.axllent.org" \
org.opencontainers.image.documentation="https://mailpit.axllent.org/docs/" \
org.opencontainers.image.licenses="MIT"
COPY --from=builder /mailpit /mailpit COPY --from=builder /mailpit /mailpit
RUN apk add --no-cache tzdata RUN apk add --no-cache tzdata
EXPOSE 1025/tcp 1110/tcp 8025/tcp EXPOSE 1025/tcp 1110/tcp 8025/tcp
HEALTHCHECK --interval=15s CMD /mailpit readyz
ENTRYPOINT ["/mailpit"] ENTRYPOINT ["/mailpit"]

View File

@ -49,9 +49,7 @@ The --recent flag will only consider files with a modification date within the l
return nil return nil
} }
info.ModTime() if ingestRecent > 0 && time.Since(info.ModTime()) > time.Duration(ingestRecent)*24*time.Hour {
if ingestRecent > 0 && time.Now().Sub(info.ModTime()) > time.Duration(ingestRecent)*24*time.Hour {
return nil return nil
} }

75
cmd/readyz.go Normal file
View File

@ -0,0 +1,75 @@
package cmd
import (
"crypto/tls"
"fmt"
"net/http"
"os"
"path"
"strings"
"time"
"github.com/axllent/mailpit/config"
"github.com/spf13/cobra"
)
var (
useHTTPS bool
)
// readyzCmd represents the healthcheck command
var readyzCmd = &cobra.Command{
Use: "readyz",
Short: "Run a healthcheck to test if Mailpit is running",
Long: `This command connects to the /readyz endpoint of a running Mailpit server
and exits with a status of 0 if the connection is successful, else with a
status 1 if unhealthy.
If running within Docker, it should automatically detect environment
settings to determine the HTTP bind interface & port.
`,
Run: func(cmd *cobra.Command, args []string) {
webroot := strings.TrimRight(path.Join("/", config.Webroot, "/"), "/") + "/"
proto := "http"
if useHTTPS {
proto = "https"
}
uri := fmt.Sprintf("%s://%s%sreadyz", proto, config.HTTPListen, webroot)
conf := &http.Transport{
IdleConnTimeout: time.Second * 5,
ExpectContinueTimeout: time.Second * 5,
TLSHandshakeTimeout: time.Second * 5,
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: conf}
res, err := client.Get(uri)
if err != nil || res.StatusCode != 200 {
os.Exit(1)
}
},
}
func init() {
rootCmd.AddCommand(readyzCmd)
if len(os.Getenv("MP_UI_BIND_ADDR")) > 0 {
config.HTTPListen = os.Getenv("MP_UI_BIND_ADDR")
}
if len(os.Getenv("MP_WEBROOT")) > 0 {
config.Webroot = os.Getenv("MP_WEBROOT")
}
config.UITLSCert = os.Getenv("MP_UI_TLS_CERT")
if config.UITLSCert != "" {
useHTTPS = true
}
readyzCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "Set the HTTP bind interface & port")
readyzCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
readyzCmd.Flags().BoolVar(&useHTTPS, "https", useHTTPS, "Connect via HTTPS (ignores HTTPS validation)")
}

View File

@ -17,8 +17,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var cfgFile string
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "mailpit", Use: "mailpit",
@ -91,7 +89,7 @@ func init() {
rootCmd.Flags().BoolVarP(&logger.VerboseLogging, "verbose", "v", logger.VerboseLogging, "Verbose logging") rootCmd.Flags().BoolVarP(&logger.VerboseLogging, "verbose", "v", logger.VerboseLogging, "Verbose logging")
// Web UI / API // Web UI / API
rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface and port for UI") rootCmd.Flags().StringVarP(&config.HTTPListen, "listen", "l", config.HTTPListen, "HTTP bind interface & port for UI")
rootCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API") rootCmd.Flags().StringVar(&config.Webroot, "webroot", config.Webroot, "Set the webroot for web UI & API")
rootCmd.Flags().StringVar(&config.UIAuthFile, "ui-auth-file", config.UIAuthFile, "A password file for web UI & API authentication") rootCmd.Flags().StringVar(&config.UIAuthFile, "ui-auth-file", config.UIAuthFile, "A password file for web UI & API authentication")
rootCmd.Flags().StringVar(&config.UITLSCert, "ui-tls-cert", config.UITLSCert, "TLS certificate for web UI (HTTPS) - requires ui-tls-key") rootCmd.Flags().StringVar(&config.UITLSCert, "ui-tls-cert", config.UITLSCert, "TLS certificate for web UI (HTTPS) - requires ui-tls-key")

View File

@ -32,9 +32,7 @@ func RunTests(msg *storage.Message, followRedirects bool) (Response, error) {
func extractTextLinks(msg *storage.Message) []string { func extractTextLinks(msg *storage.Message) []string {
links := []string{} links := []string{}
for _, match := range linkRe.FindAllString(msg.Text, -1) { links = append(links, linkRe.FindAllString(msg.Text, -1)...)
links = append(links, match)
}
return links return links
} }

View File

@ -63,7 +63,7 @@ func pruneMessages() {
ids := []string{} ids := []string{}
var prunedSize int64 var prunedSize int64
var size int var size int
if err := q.Query(nil, db, func(row *sql.Rows) { if err := q.Query(context.TODO(), db, func(row *sql.Rows) {
var id string var id string
if err := row.Scan(&id, &size); err != nil { if err := row.Scan(&id, &size); err != nil {

View File

@ -2,6 +2,7 @@
package storage package storage
import ( import (
"context"
"database/sql" "database/sql"
"fmt" "fmt"
"os" "os"
@ -114,6 +115,11 @@ func Close() {
} }
} }
// Ping the database connection and return an error if unsuccessful
func Ping() error {
return db.Ping()
}
// StatsGet returns the total/unread statistics for a mailbox // StatsGet returns the total/unread statistics for a mailbox
func StatsGet() MailboxStats { func StatsGet() MailboxStats {
var ( var (
@ -137,7 +143,7 @@ func CountTotal() int {
_ = sqlf.From("mailbox"). _ = sqlf.From("mailbox").
Select("COUNT(*)").To(&total). Select("COUNT(*)").To(&total).
QueryRowAndClose(nil, db) QueryRowAndClose(context.TODO(), db)
return total return total
} }
@ -146,11 +152,10 @@ func CountTotal() int {
func CountUnread() int { func CountUnread() int {
var total int var total int
q := sqlf.From("mailbox"). _ = sqlf.From("mailbox").
Select("COUNT(*)").To(&total). Select("COUNT(*)").To(&total).
Where("Read = ?", 0) Where("Read = ?", 0).
QueryRowAndClose(context.TODO(), db)
_ = q.QueryRowAndClose(nil, db)
return total return total
} }
@ -159,26 +164,23 @@ func CountUnread() int {
func CountRead() int { func CountRead() int {
var total int var total int
q := sqlf.From("mailbox"). _ = sqlf.From("mailbox").
Select("COUNT(*)").To(&total). Select("COUNT(*)").To(&total).
Where("Read = ?", 1) Where("Read = ?", 1).
QueryRowAndClose(context.TODO(), db)
_ = q.QueryRowAndClose(nil, db)
return total return total
} }
// IsUnread returns the number of emails in the database that are unread. // IsUnread returns whether a message is unread or not.
// If an ID is supplied, then it is just limited to that message.
func IsUnread(id string) bool { func IsUnread(id string) bool {
var unread int var unread int
q := sqlf.From("mailbox"). _ = sqlf.From("mailbox").
Select("COUNT(*)").To(&unread). Select("COUNT(*)").To(&unread).
Where("Read = ?", 0). Where("Read = ?", 0).
Where("ID = ?", id) Where("ID = ?", id).
QueryRowAndClose(context.TODO(), db)
_ = q.QueryRowAndClose(nil, db)
return unread == 1 return unread == 1
} }
@ -187,11 +189,10 @@ func IsUnread(id string) bool {
func MessageIDExists(id string) bool { func MessageIDExists(id string) bool {
var total int var total int
q := sqlf.From("mailbox"). _ = sqlf.From("mailbox").
Select("COUNT(*)").To(&total). Select("COUNT(*)").To(&total).
Where("MessageID = ?", id) Where("MessageID = ?", id).
QueryRowAndClose(context.TODO(), db)
_ = q.QueryRowAndClose(nil, db)
return total != 0 return total != 0
} }

View File

@ -154,7 +154,7 @@ func List(start, limit int) ([]MessageSummary, error) {
Limit(limit). Limit(limit).
Offset(start) Offset(start)
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) { if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
var created int64 var created int64
var id string var id string
var messageID string var messageID string
@ -245,7 +245,7 @@ func GetMessage(id string) (*Message, error) {
Select(`Created`). Select(`Created`).
Where(`ID = ?`, id) Where(`ID = ?`, id)
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) { if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
var created int64 var created int64
if err := row.Scan(&created); err != nil { if err := row.Scan(&created); err != nil {
@ -564,7 +564,7 @@ func DeleteAllMessages() error {
_ = sqlf.From("mailbox"). _ = sqlf.From("mailbox").
Select("COUNT(*)").To(&total). Select("COUNT(*)").To(&total).
QueryRowAndClose(nil, db) QueryRowAndClose(context.TODO(), db)
// begin a transaction to ensure both the message // begin a transaction to ensure both the message
// summaries and data are deleted successfully // summaries and data are deleted successfully

View File

@ -1,6 +1,7 @@
package storage package storage
import ( import (
"context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
@ -140,7 +141,7 @@ func migrateTagsToManyMany() {
Where("Tags != ?", "[]"). Where("Tags != ?", "[]").
Where("Tags IS NOT NULL") Where("Tags IS NOT NULL")
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) { if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
var id string var id string
var jsonTags string var jsonTags string
if err := row.Scan(&id, &jsonTags); err != nil { if err := row.Scan(&id, &jsonTags); err != nil {
@ -169,7 +170,7 @@ func migrateTagsToManyMany() {
if _, err := sqlf.Update("mailbox"). if _, err := sqlf.Update("mailbox").
Set("Tags", nil). Set("Tags", nil).
Where("ID = ?", id). Where("ID = ?", id).
ExecAndClose(nil, db); err != nil { ExecAndClose(context.TODO(), db); err != nil {
logger.Log().Errorf("[migration] %s", err.Error()) logger.Log().Errorf("[migration] %s", err.Error())
} }
} }
@ -182,7 +183,7 @@ func migrateTagsToManyMany() {
if _, err := sqlf.Update("mailbox"). if _, err := sqlf.Update("mailbox").
Set("Tags", nil). Set("Tags", nil).
Where("Tags = ?", "[]"). Where("Tags = ?", "[]").
ExecAndClose(nil, db); err != nil { ExecAndClose(context.TODO(), db); err != nil {
logger.Log().Errorf("[migration] %s", err.Error()) logger.Log().Errorf("[migration] %s", err.Error())
} }
} }

View File

@ -26,7 +26,7 @@ func ReindexAll() {
err := sqlf.Select("ID").To(&i). err := sqlf.Select("ID").To(&i).
From("mailbox"). From("mailbox").
OrderBy("Created DESC"). OrderBy("Created DESC").
QueryAndClose(nil, db, func(row *sql.Rows) { QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
ids = append(ids, i) ids = append(ids, i)
}) })

View File

@ -29,7 +29,7 @@ func Search(search string, start, limit int) ([]MessageSummary, int, error) {
q := searchQueryBuilder(search) q := searchQueryBuilder(search)
var err error var err error
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) { if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
var created int64 var created int64
var id string var id string
var messageID string var messageID string
@ -101,7 +101,7 @@ func DeleteSearch(search string) error {
ids := []string{} ids := []string{}
deleteSize := 0 deleteSize := 0
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) { if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
var created int64 var created int64
var id string var id string
var messageID string var messageID string

View File

@ -1,6 +1,7 @@
package storage package storage
import ( import (
"context"
"database/sql" "database/sql"
"github.com/axllent/mailpit/internal/logger" "github.com/axllent/mailpit/internal/logger"
@ -14,7 +15,7 @@ func SettingGet(k string) string {
Select("Value").To(&result). Select("Value").To(&result).
Where("Key = ?", k). Where("Key = ?", k).
Limit(1). Limit(1).
QueryAndClose(nil, db, func(row *sql.Rows) {}) QueryAndClose(context.TODO(), db, func(row *sql.Rows) {})
if err != nil { if err != nil {
logger.Log().Errorf("[db] %s", err.Error()) logger.Log().Errorf("[db] %s", err.Error())
return "" return ""
@ -40,7 +41,7 @@ func getDeletedSize() int64 {
Select("Value").To(&result). Select("Value").To(&result).
Where("Key = ?", "DeletedSize"). Where("Key = ?", "DeletedSize").
Limit(1). Limit(1).
QueryAndClose(nil, db, func(row *sql.Rows) {}) QueryAndClose(context.TODO(), db, func(row *sql.Rows) {})
if err != nil { if err != nil {
logger.Log().Errorf("[db] %s", err.Error()) logger.Log().Errorf("[db] %s", err.Error())
return 0 return 0
@ -54,7 +55,7 @@ func totalMessagesSize() int64 {
var result sql.NullInt64 var result sql.NullInt64
err := sqlf.From("mailbox"). err := sqlf.From("mailbox").
Select("SUM(Size)").To(&result). Select("SUM(Size)").To(&result).
QueryAndClose(nil, db, func(row *sql.Rows) {}) QueryAndClose(context.TODO(), db, func(row *sql.Rows) {})
if err != nil { if err != nil {
logger.Log().Errorf("[db] %s", err.Error()) logger.Log().Errorf("[db] %s", err.Error())
return 0 return 0

View File

@ -1,6 +1,7 @@
package storage package storage
import ( import (
"context"
"database/sql" "database/sql"
"regexp" "regexp"
"sort" "sort"
@ -64,14 +65,14 @@ func AddMessageTag(id, name string) error {
Where("Name = ?", name) Where("Name = ?", name)
// tag exists - add tag to message // tag exists - add tag to message
if err := q.QueryRowAndClose(nil, db); err == nil { if err := q.QueryRowAndClose(context.TODO(), db); err == nil {
// check message does not already have this tag // check message does not already have this tag
var count int var count int
if _, err := sqlf.From("message_tags"). if _, err := sqlf.From("message_tags").
Select("COUNT(ID)").To(&count). Select("COUNT(ID)").To(&count).
Where("ID = ?", id). Where("ID = ?", id).
Where("TagID = ?", tagID). Where("TagID = ?", tagID).
ExecAndClose(nil, db); err != nil { ExecAndClose(context.TODO(), db); err != nil {
return err return err
} }
if count != 0 { if count != 0 {
@ -84,7 +85,7 @@ func AddMessageTag(id, name string) error {
_, err := sqlf.InsertInto("message_tags"). _, err := sqlf.InsertInto("message_tags").
Set("ID", id). Set("ID", id).
Set("TagID", tagID). Set("TagID", tagID).
ExecAndClose(nil, db) ExecAndClose(context.TODO(), db)
return err return err
} }
@ -94,7 +95,7 @@ func AddMessageTag(id, name string) error {
if err := sqlf.InsertInto("tags"). if err := sqlf.InsertInto("tags").
Set("Name", name). Set("Name", name).
Returning("ID").To(&tagID). Returning("ID").To(&tagID).
QueryRowAndClose(nil, db); err != nil { QueryRowAndClose(context.TODO(), db); err != nil {
return err return err
} }
@ -104,7 +105,7 @@ func AddMessageTag(id, name string) error {
Select("COUNT(ID)").To(&count). Select("COUNT(ID)").To(&count).
Where("ID = ?", id). Where("ID = ?", id).
Where("TagID = ?", tagID). Where("TagID = ?", tagID).
ExecAndClose(nil, db); err != nil { ExecAndClose(context.TODO(), db); err != nil {
return err return err
} }
if count != 0 { if count != 0 {
@ -115,7 +116,7 @@ func AddMessageTag(id, name string) error {
_, err := sqlf.InsertInto("message_tags"). _, err := sqlf.InsertInto("message_tags").
Set("ID", id). Set("ID", id).
Set("TagID", tagID). Set("TagID", tagID).
ExecAndClose(nil, db) ExecAndClose(context.TODO(), db)
return err return err
} }
@ -124,7 +125,7 @@ func DeleteMessageTag(id, name string) error {
if _, err := sqlf.DeleteFrom("message_tags"). if _, err := sqlf.DeleteFrom("message_tags").
Where("message_tags.ID = ?", id). Where("message_tags.ID = ?", id).
Where(`message_tags.Key IN (SELECT Key FROM message_tags LEFT JOIN tags ON TagID=tags.ID WHERE Name = ?)`, name). Where(`message_tags.Key IN (SELECT Key FROM message_tags LEFT JOIN tags ON TagID=tags.ID WHERE Name = ?)`, name).
ExecAndClose(nil, db); err != nil { ExecAndClose(context.TODO(), db); err != nil {
return err return err
} }
@ -135,7 +136,7 @@ func DeleteMessageTag(id, name string) error {
func DeleteAllMessageTags(id string) error { func DeleteAllMessageTags(id string) error {
if _, err := sqlf.DeleteFrom("message_tags"). if _, err := sqlf.DeleteFrom("message_tags").
Where("message_tags.ID = ?", id). Where("message_tags.ID = ?", id).
ExecAndClose(nil, db); err != nil { ExecAndClose(context.TODO(), db); err != nil {
return err return err
} }
@ -151,7 +152,7 @@ func GetAllTags() []string {
Select(`DISTINCT Name`). Select(`DISTINCT Name`).
From("tags").To(&name). From("tags").To(&name).
OrderBy("Name"). OrderBy("Name").
QueryAndClose(nil, db, func(row *sql.Rows) { QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
tags = append(tags, name) tags = append(tags, name)
}); err != nil { }); err != nil {
logger.Log().Errorf("[db] %s", err.Error()) logger.Log().Errorf("[db] %s", err.Error())
@ -173,7 +174,7 @@ func GetAllTagsCount() map[string]int64 {
LeftJoin("message_tags", "tags.ID = message_tags.TagID"). LeftJoin("message_tags", "tags.ID = message_tags.TagID").
GroupBy("message_tags.TagID"). GroupBy("message_tags.TagID").
OrderBy("Name"). OrderBy("Name").
QueryAndClose(nil, db, func(row *sql.Rows) { QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
tags[name] = total tags[name] = total
// tags = append(tags, name) // tags = append(tags, name)
}); err != nil { }); err != nil {
@ -192,7 +193,7 @@ func pruneUnusedTags() error {
toDel := []int{} toDel := []int{}
if err := q.QueryAndClose(nil, db, func(row *sql.Rows) { if err := q.QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
var n string var n string
var id int var id int
var c int var c int
@ -214,7 +215,7 @@ func pruneUnusedTags() error {
for _, id := range toDel { for _, id := range toDel {
if _, err := sqlf.DeleteFrom("tags"). if _, err := sqlf.DeleteFrom("tags").
Where("ID = ?", id). Where("ID = ?", id).
ExecAndClose(nil, db); err != nil { ExecAndClose(context.TODO(), db); err != nil {
return err return err
} }
} }
@ -282,7 +283,7 @@ func getMessageTags(id string) []string {
LeftJoin("message_tags", "Tags.ID=message_tags.TagID"). LeftJoin("message_tags", "Tags.ID=message_tags.TagID").
Where(`message_tags.ID = ?`, id). Where(`message_tags.ID = ?`, id).
OrderBy("Name"). OrderBy("Name").
QueryAndClose(nil, db, func(row *sql.Rows) { QueryAndClose(context.TODO(), db, func(row *sql.Rows) {
tags = append(tags, name) tags = append(tags, name)
}); err != nil { }); err != nil {
logger.Log().Errorf("[tags] %s", err.Error()) logger.Log().Errorf("[tags] %s", err.Error())

View File

@ -103,42 +103,3 @@ func inArray(k string, arr []string) bool {
func escPercentChar(s string) string { func escPercentChar(s string) string {
return strings.ReplaceAll(s, "%", "%%") return strings.ReplaceAll(s, "%", "%%")
} }
// Escape certain characters in search phrases
func escSearch(str string) string {
dest := make([]byte, 0, 2*len(str))
var escape byte
for i := 0; i < len(str); i++ {
c := str[i]
escape = 0
switch c {
case 0: /* Must be escaped for 'mysql' */
escape = '0'
break
case '\n': /* Must be escaped for logs */
escape = 'n'
break
case '\r':
escape = 'r'
break
case '\\':
escape = '\\'
break
case '\'':
escape = '\''
break
case '\032': //十进制26,八进制32,十六进制1a, /* This gives problems on Win32 */
escape = 'Z'
}
if escape != 0 {
dest = append(dest, '\\', escape)
} else {
dest = append(dest, c)
}
}
return string(dest)
}

View File

@ -98,53 +98,6 @@ func makeAbsolute(inputFilePath, outputFilePath string) (string, string, error)
return inputFilePath, outputFilePath, err return inputFilePath, outputFilePath, err
} }
// Write path without the prefix in subPath to tar writer.
func writeTarGz(path string, tarWriter *tar.Writer, fileInfo os.FileInfo, subPath string) error {
file, err := os.Open(filepath.Clean(path))
if err != nil {
return err
}
defer func() {
if err := file.Close(); err != nil {
fmt.Printf("Error closing file: %s\n", err)
}
}()
evaledPath, err := filepath.EvalSymlinks(path)
if err != nil {
return err
}
subPath, err = filepath.EvalSymlinks(subPath)
if err != nil {
return err
}
link := ""
if evaledPath != path {
link = evaledPath
}
header, err := tar.FileInfoHeader(fileInfo, link)
if err != nil {
return err
}
header.Name = evaledPath[len(subPath):]
err = tarWriter.WriteHeader(header)
if err != nil {
return err
}
_, err = io.Copy(tarWriter, file)
if err != nil {
return err
}
return err
}
// Extract the file in filePath to directory. // Extract the file in filePath to directory.
func extract(filePath string, directory string) error { func extract(filePath string, directory string) error {
file, err := os.Open(filepath.Clean(filePath)) file, err := os.Open(filepath.Clean(filePath))
@ -200,7 +153,7 @@ func extract(filePath string, directory string) error {
// set file ownership (if allowed) // set file ownership (if allowed)
// Chtimes() && Chmod() only set after once extraction is complete // Chtimes() && Chmod() only set after once extraction is complete
os.Chown(filename, header.Uid, header.Gid) // #nosec _ = os.Chown(filename, header.Uid, header.Gid)
// add directory info to slice to process afterwards // add directory info to slice to process afterwards
postExtraction = append(postExtraction, DirInfo{filename, header}) postExtraction = append(postExtraction, DirInfo{filename, header})
@ -249,15 +202,15 @@ func extract(filePath string, directory string) error {
} }
// set file permissions, timestamps & uid/gid // set file permissions, timestamps & uid/gid
os.Chmod(filename, os.FileMode(header.Mode)) // #nosec _ = os.Chmod(filename, os.FileMode(header.Mode))
os.Chtimes(filename, header.AccessTime, header.ModTime) // #nosec _ = os.Chtimes(filename, header.AccessTime, header.ModTime)
os.Chown(filename, header.Uid, header.Gid) // #nosec _ = os.Chown(filename, header.Uid, header.Gid)
} }
if len(postExtraction) > 0 { if len(postExtraction) > 0 {
for _, dir := range postExtraction { for _, dir := range postExtraction {
os.Chtimes(dir.Path, dir.Header.AccessTime, dir.Header.ModTime) // #nosec _ = os.Chtimes(dir.Path, dir.Header.AccessTime, dir.Header.ModTime)
os.Chmod(dir.Path, dir.Header.FileInfo().Mode().Perm()) // #nosec _ = os.Chmod(dir.Path, dir.Header.FileInfo().Mode().Perm())
} }
} }

View File

@ -335,16 +335,6 @@ func mkDirIfNotExists(path string) error {
return nil return nil
} }
// IsFile returns if a path is a file
func isFile(path string) bool {
info, err := os.Stat(path)
if os.IsNotExist(err) || !info.Mode().IsRegular() {
return false
}
return true
}
// IsDir returns if a path is a directory // IsDir returns if a path is a directory
func isDir(path string) bool { func isDir(path string) bool {
info, err := os.Stat(path) info, err := os.Stat(path)

View File

@ -114,7 +114,7 @@ func blankImage(a *enmime.Part, w http.ResponseWriter) {
rect := image.Rect(0, 0, thumbWidth, thumbHeight) rect := image.Rect(0, 0, thumbWidth, thumbHeight)
img := image.NewRGBA(rect) img := image.NewRGBA(rect)
background := color.RGBA{255, 255, 255, 255} background := color.RGBA{255, 255, 255, 255}
draw.Draw(img, img.Bounds(), &image.Uniform{background}, image.ZP, draw.Src) draw.Draw(img, img.Bounds(), &image.Uniform{background}, image.Point{}, draw.Src)
var b bytes.Buffer var b bytes.Buffer
foo := bufio.NewWriter(&b) foo := bufio.NewWriter(&b)
dstImageFill := imaging.Fill(img, thumbWidth, thumbHeight, imaging.Center, imaging.Lanczos) dstImageFill := imaging.Fill(img, thumbWidth, thumbHeight, imaging.Center, imaging.Lanczos)

View File

@ -3,12 +3,14 @@ package handlers
import ( import (
"net/http" "net/http"
"sync/atomic" "sync/atomic"
"github.com/axllent/mailpit/internal/storage"
) )
// ReadyzHandler is a ready probe that signals k8s to be able to retrieve traffic // ReadyzHandler is a ready probe that signals k8s to be able to retrieve traffic
func ReadyzHandler(isReady *atomic.Value) http.HandlerFunc { func ReadyzHandler(isReady *atomic.Value) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) { return func(w http.ResponseWriter, _ *http.Request) {
if isReady == nil || !isReady.Load().(bool) { if isReady == nil || !isReady.Load().(bool) || storage.Ping() != nil {
http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable) http.Error(w, http.StatusText(http.StatusServiceUnavailable), http.StatusServiceUnavailable)
return return
} }

View File

@ -41,9 +41,9 @@ func Run() {
var err error var err error
if config.POP3TLSCert != "" { if config.POP3TLSCert != "" {
cer, err := tls.LoadX509KeyPair(config.POP3TLSCert, config.POP3TLSKey) cer, err2 := tls.LoadX509KeyPair(config.POP3TLSCert, config.POP3TLSKey)
if err != nil { if err2 != nil {
logger.Log().Errorf("[pop3] %s", err.Error()) logger.Log().Errorf("[pop3] %s", err2.Error())
return return
} }
@ -273,6 +273,10 @@ func handleClient(conn net.Conn) {
m := messages[nr-1] m := messages[nr-1]
headers, body, err := getTop(m.ID, lines) headers, body, err := getTop(m.ID, lines)
if err != nil {
sendResponse(conn, err.Error())
return
}
sendData(conn, "+OK Top of message follows") sendData(conn, "+OK Top of message follows")
sendData(conn, headers+"\r\n") sendData(conn, headers+"\r\n")

View File

@ -47,8 +47,6 @@ func TestAPIv1Messages(t *testing.T) {
insertEmailData(t) insertEmailData(t)
assertStatsEqual(t, ts.URL+"/api/v1/messages", 100, 100) assertStatsEqual(t, ts.URL+"/api/v1/messages", 100, 100)
// store this for later tests
m, err = fetchMessages(ts.URL + "/api/v1/messages") m, err = fetchMessages(ts.URL + "/api/v1/messages")
if err != nil { if err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
@ -56,7 +54,6 @@ func TestAPIv1Messages(t *testing.T) {
// read first 10 messages // read first 10 messages
t.Log("Read first 10 messages including raw & headers") t.Log("Read first 10 messages including raw & headers")
putIDS := []string{}
for idx, msg := range m.Messages { for idx, msg := range m.Messages {
if idx == 10 { if idx == 10 {
break break
@ -71,13 +68,10 @@ func TestAPIv1Messages(t *testing.T) {
t.Errorf(err.Error()) t.Errorf(err.Error())
} }
// het headers // get headers
if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID + "/headers"); err != nil { if _, err := clientGet(ts.URL + "/api/v1/message/" + msg.ID + "/headers"); err != nil {
t.Errorf(err.Error()) t.Errorf(err.Error())
} }
// store for later
putIDS = append(putIDS, msg.ID)
} }
// 10 should be marked as read // 10 should be marked as read

View File

@ -22,14 +22,10 @@ const (
// Send pings to peer with this period. Must be less than pongWait. // Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10 pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer.
maxMessageSize = 512
) )
var ( var (
newline = []byte{'\n'} newline = []byte{'\n'}
space = []byte{' '}
// MessageHub global // MessageHub global
MessageHub *Hub MessageHub *Hub