1
0
mirror of https://github.com/axllent/mailpit.git synced 2025-08-13 20:04:49 +02:00

Merge branch 'release/v1.23.2'

This commit is contained in:
Ralph Slooten
2025-03-16 21:16:09 +13:00
12 changed files with 620 additions and 482 deletions

View File

@@ -48,6 +48,6 @@ jobs:
axllent/mailpit:latest
axllent/mailpit:${{ github.ref_name }}
axllent/mailpit:v${{ steps.semver_parser.outputs.major }}.${{ steps.semver_parser.outputs.minor }}
ghcr.io/${{ github.repository }}:latest
ghcr.io/${{ github.repository }}:${{ github.ref_name }}
ghcr.io/${{ github.repository }}:v${{ steps.semver_parser.outputs.major }}.${{ steps.semver_parser.outputs.minor }}
ghcr.io/${{ github.repository }}:latest

View File

@@ -26,7 +26,7 @@ jobs:
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- run: go test -p 1 ./internal/storage ./server ./internal/smtpd ./internal/pop3 ./internal/tools ./internal/html2text ./internal/linkcheck -v
- run: go test -p 1 ./internal/storage ./server ./internal/smtpd ./internal/pop3 ./internal/tools ./internal/html2text ./internal/htmlcheck ./internal/linkcheck -v
- run: go test -p 1 ./internal/storage ./internal/html2text -bench=.
# build the assets

View File

@@ -2,6 +2,18 @@
Notable changes to Mailpit will be documented in this file.
## [v1.23.2]
### Chore
- Update Go dependencies
- Improve inline HTML Check style detection ([#467](https://github.com/axllent/mailpit/issues/467))
- Use `Message-ID` header instead of `Message-Id` when generating new IDs (RFC 5322)
- Update node dependencies
### Testing
- Add tests for inline HTML Checks
## [v1.23.1]
### Chore

View File

@@ -90,7 +90,7 @@ func init() {
rootCmd.Flags().IntVarP(&config.MaxMessages, "max", "m", config.MaxMessages, "Max number of messages to store")
rootCmd.Flags().StringVar(&config.MaxAge, "max-age", config.MaxAge, "Max age of messages in either (h)ours or (d)ays (eg: 3d)")
rootCmd.Flags().BoolVar(&config.UseMessageDates, "use-message-dates", config.UseMessageDates, "Use message dates as the received dates")
rootCmd.Flags().BoolVar(&config.IgnoreDuplicateIDs, "ignore-duplicate-ids", config.IgnoreDuplicateIDs, "Ignore duplicate messages (by Message-Id)")
rootCmd.Flags().BoolVar(&config.IgnoreDuplicateIDs, "ignore-duplicate-ids", config.IgnoreDuplicateIDs, "Ignore duplicate messages (by Message-ID)")
rootCmd.Flags().StringVar(&logger.LogFile, "log-file", logger.LogFile, "Log output to file instead of stdout")
rootCmd.Flags().BoolVarP(&logger.QuietLogging, "quiet", "q", logger.QuietLogging, "Quiet logging (errors only)")
rootCmd.Flags().BoolVarP(&logger.VerboseLogging, "verbose", "v", logger.VerboseLogging, "Verbose logging")

4
go.mod
View File

@@ -8,7 +8,7 @@ require (
github.com/PuerkitoBio/goquery v1.10.2
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de
github.com/axllent/semver v0.0.1
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b
github.com/gorilla/mux v1.8.1
github.com/gorilla/websocket v1.5.3
github.com/jhillyerd/enmime v1.3.0
@@ -27,7 +27,7 @@ require (
golang.org/x/text v0.23.0
golang.org/x/time v0.11.0
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.36.0
modernc.org/sqlite v1.36.1
)
require (

8
go.sum
View File

@@ -24,8 +24,8 @@ github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f h1:3BSP1Tbs2djlpprl7wCLuiqMaUh5SJkkzI2gDs+FgLs=
github.com/gogs/chardet v0.0.0-20211120154057-b7413eaefb8f/go.mod h1:Pcatq5tYkCW2Q6yrR2VRHlbHpZ/R4/7qyL1TCF7vl14=
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e h1:ESHlT0RVZphh4JGBz49I5R6nTdC8Qyc08vU25GQHzzQ=
github.com/gomarkdown/markdown v0.0.0-20250207164621-7a1f277a159e/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b h1:EY/KpStFl60qA17CptGXhwfZ+k1sFNJIUNR8DdbcuUk=
github.com/gomarkdown/markdown v0.0.0-20250311123330-531bef5e742b/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
@@ -244,8 +244,8 @@ modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8=
modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
modernc.org/sqlite v1.36.1 h1:bDa8BJUH4lg6EGkLbahKe/8QqoF8p9gArSc6fTqYhyQ=
modernc.org/sqlite v1.36.1/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=

View File

@@ -38,167 +38,178 @@ var htmlTests = map[string]string{
// Image tests using regex to match against img[src]
var imageRegexpTests = map[string]*regexp.Regexp{
"image-apng": regexp.MustCompile(`(?i)\.apng$`), // 78.723404
"image-avif": regexp.MustCompile(`(?i)\.avif$`), // 14.864864
"image-base64": regexp.MustCompile(`^(?i)data:image\/`), // 61.702126
"image-bmp": regexp.MustCompile(`(?i)\.bmp$`), // 89.3617
"image-gif": regexp.MustCompile(`(?i)\.gif$`), // 89.3617
"image-hdr": regexp.MustCompile(`(?i)\.hdr$`), // 12.5
"image-heif": regexp.MustCompile(`(?i)\.heif$`), // 0
"image-ico": regexp.MustCompile(`(?i)\.ico$`), // 87.23404
"image-mp4": regexp.MustCompile(`(?i)\.mp4$`), // 26.53061
"image-ppm": regexp.MustCompile(`(?i)\.ppm$`), // 2.0833282
"image-svg": regexp.MustCompile(`(?i)\.svg$`), // 64.91228
"image-tiff": regexp.MustCompile(`(?i)\.tiff?$`), // 38.29787
"image-webp": regexp.MustCompile(`(?i)\.webp$`), // 59.649124
"image-apng": regexp.MustCompile(`(?i)\.apng$`),
"image-avif": regexp.MustCompile(`(?i)\.avif$`),
"image-base64": regexp.MustCompile(`^(?i)data:image\/`),
"image-bmp": regexp.MustCompile(`(?i)\.bmp$`),
"image-gif": regexp.MustCompile(`(?i)\.gif$`),
"image-hdr": regexp.MustCompile(`(?i)\.hdr$`),
"image-heif": regexp.MustCompile(`(?i)\.heif$`),
"image-ico": regexp.MustCompile(`(?i)\.ico$`),
"image-mp4": regexp.MustCompile(`(?i)\.mp4$`),
"image-ppm": regexp.MustCompile(`(?i)\.ppm$`),
"image-svg": regexp.MustCompile(`(?i)\.svg$`),
"image-tiff": regexp.MustCompile(`(?i)\.tiff?$`),
"image-webp": regexp.MustCompile(`(?i)\.webp$`),
}
var cssInlineTests = map[string]string{
"css-accent-color": "[style*=\"accent-color:\"]", // 6.6666718
"css-align-items": "[style*=\"align-items:\"]", // 60.784313
"css-aspect-ratio": "[style*=\"aspect-ratio:\"]", // 30
"css-background-blend-mode": "[style*=\"background-blend-mode:\"]", // 61.70213
"css-background-clip": "[style*=\"background-clip:\"]", // 61.70213
"css-background-color": "[style*=\"background-color:\"], [bgcolor]", // 90
"css-background-image": "[style*=\"background-image:\"]", // 57.62712
"css-background-origin": "[style*=\"background-origin:\"]", // 61.70213
"css-background-position": "[style*=\"background-position:\"]", // 61.224487
"css-background-repeat": "[style*=\"background-repeat:\"]", // 67.34694
"css-background-size": "[style*=\"background-size:\"]", // 61.702126
"css-background": "[style*=\"background:\"], [background]", // 57.407406
"css-block-inline-size": "[style*=\"block-inline-size:\"]", // 46.93877
"css-border-image": "[style*=\"border-image:\"]", // 52.173912
"css-border-inline-block-individual": "[style*=\"border-inline:\"]", // 18.518517
"css-border-radius": "[style*=\"border-radius:\"]", // 67.34694
"css-border": "[style*=\"border:\"], [border]", // 86.95652
"css-box-shadow": "[style*=\"box-shadow:\"]", // 43.103447
"css-box-sizing": "[style*=\"box-sizing:\"]", // 71.739136
"css-caption-side": "[style*=\"caption-side:\"]", // 84
"css-clip-path": "[style*=\"clip-path:\"]", // 43.396225
"css-column-count": "[style*=\"column-count:\"]", // 67.391304
"css-column-layout-properties": "[style*=\"column-layout-properties:\"]", // 47.368423
"css-conic-gradient": "[style*=\"conic-gradient:\"]", // 38.461536
"css-direction": "[style*=\"direction:\"]", // 97.77778
"css-display-flex": "[style*=\"display:flex\"]", // 53.448277
"css-display-grid": "[style*=\"display:grid\"]", // 54.347824
"css-display-none": "[style*=\"display:none\"]", // 84.78261
"css-display": "[style*=\"display:\"]", // 55.555553
"css-filter": "[style*=\"filter:\"]", // 50
"css-flex-direction": "[style*=\"flex-direction:\"]", // 50
"css-flex-wrap": "[style*=\"flex-wrap:\"]", // 49.09091
"css-float": "[style*=\"float:\"]", // 85.10638
"css-font-kerning": "[style*=\"font-kerning:\"]", // 66.666664
"css-font-weight": "[style*=\"font-weight:\"]", // 76.666664
"css-font": "[style*=\"font:\"]", // 95.833336
"css-gap": "[style*=\"gap:\"]", // 40
"css-grid-template": "[style*=\"grid-template:\"]", // 34.042553
"css-height": "[style*=\"height:\"], [height]", // 77.08333
"css-hyphens": "[style*=\"hyphens:\"]", // 31.111107
"css-important": "[style*=\"!important\"]", // 43.478264
"css-inline-size": "[style*=\"inline-size:\"]", // 43.478264
"css-intrinsic-size": "[style*=\"intrinsic-size:\"]", // 40.54054
"css-justify-content": "[style*=\"justify-content:\"]", // 59.25926
"css-letter-spacing": "[style*=\"letter-spacing:\"]", // 87.23404
"css-line-height": "[style*=\"line-height:\"]", // 82.608696
"css-list-style-image": "[style*=\"list-style-image:\"]", // 54.16667
"css-list-style-position": "[style*=\"list-style-position:\"]", // 87.5
"css-list-style": "[style*=\"list-style:\"]", // 62.500004
"css-margin-block-start-end": "[style*=\"margin-block-start:\"], [style*=\"margin-block-end:\"]", // 32.07547
"css-margin-inline-block": "[style*=\"margin-inline-block:\"]", // 16.981125
"css-margin-inline-start-end": "[style*=\"margin-inline-start:\"], [style*=\"margin-inline-end:\"]", // 32.07547
"css-margin-inline": "[style*=\"margin-inline:\"]", // 43.39623
"css-margin": "[style*=\"margin:\"]", // 71.42857
"css-max-block-size": "[style*=\"max-block-size:\"]", // 35.714287
"css-max-height": "[style*=\"max-height:\"]", // 86.95652
"css-max-width": "[style*=\"max-width:\"]", // 76.47058
"css-min-height": "[style*=\"min-height:\"]", // 82.608696
"css-min-inline-size": "[style*=\"min-inline-size:\"]", // 33.33333
"css-min-width": "[style*=\"min-width:\"]", // 86.95652
"css-mix-blend-mode": "[style*=\"mix-blend-mode:\"]", // 62.745094
"css-modern-color": "[style*=\"modern-color:\"]", // 10.81081
"css-object-fit": "[style*=\"object-fit:\"]", // 57.142857
"css-object-position": "[style*=\"object-position:\"]", // 55.10204
"css-opacity": "[style*=\"opacity:\"]", // 63.04348
"css-outline-offset": "[style*=\"outline-offset:\"]", // 42.5
"css-outline": "[style*=\"outline:\"]", // 80.85106
"css-overflow-wrap": "[style*=\"overflow-wrap:\"]", // 6.6666603
"css-overflow": "[style*=\"overflow:\"]", // 78.26087
"css-padding-block-start-end": "[style*=\"padding-block-start:\"], [style*=\"padding-block-end:\"]", // 32.07547
"css-padding-inline-block": "[style*=\"padding-inline-block:\"]", // 16.981125
"css-padding-inline-start-end": "[style*=\"padding-inline-start:\"], [style*=\"padding-inline-end:\"]", // 32.07547
"css-padding": "[style*=\"padding:\"], [padding]", // 87.755104
"css-position": "[style*=\"position:\"]", // 19.56522
"css-radial-gradient": "[style*=\"radial-gradient:\"]", // 64.583336
"css-rgb": "[style*=\"rgb(\"]", // 53.846153
"css-rgba": "[style*=\"rgba(\"]", // 56
"css-scroll-snap": "[style*=\"roll-snap:\"]", // 38.88889
"css-tab-size": "[style*=\"tab-size:\"]", // 32.075474
"css-table-layout": "[style*=\"table-layout:\"]", // 53.33333
"css-text-align-last": "[style*=\"text-align-last:\"]", // 42.307693
"css-text-align": "[style*=\"text-align:\"]", // 60.416664
"css-text-decoration-color": "[style*=\"text-decoration-color:\"]", // 67.34695
"css-text-decoration-thickness": "[style*=\"text-decoration-thickness:\"]", // 38.333336
"css-text-decoration": "[style*=\"text-decoration:\"]", // 67.391304
"css-text-emphasis-position": "[style*=\"text-emphasis-position:\"]", // 28.571434
"css-text-emphasis": "[style*=\"text-emphasis:\"]", // 36.734695
"css-text-indent": "[style*=\"text-indent:\"]", // 78.43137
"css-text-overflow": "[style*=\"text-overflow:\"]", // 58.695656
"css-text-shadow": "[style*=\"text-shadow:\"]", // 69.565216
"css-text-transform": "[style*=\"text-transform:\"]", // 86.666664
"css-text-underline-offset": "[style*=\"text-underline-offset:\"]", // 39.285713
"css-transform": "[style*=\"transform:\"]", // 50
"css-unit-calc": "[style*=\"calc(:\"]", // 56.25
"css-variables": "[style*=\"variables:\"]", // 46.551727
"css-visibility": "[style*=\"visibility:\"]", // 52.173916
"css-white-space": "[style*=\"white-space:\"]", // 58.69565
"css-width": "[style*=\"width:\"], [width]", // 87.5
"css-word-break": "[style*=\"word-break:\"]", // 28.888887
"css-writing-mode": "[style*=\"writing-mode:\"]", // 56.25
"css-z-index": "[style*=\"z-index:\"]", // 76.08696
// inline attribute <match>=""
var styleInlineAttributes = map[string]string{
"css-background-color": "[bgcolor]",
"css-background": "[background]",
"css-border": "[border]",
"css-height": "[height]",
"css-padding": "[padding]",
"css-width": "[width]",
}
// inline style="<match>"
var cssInlineRegexTests = map[string]*regexp.Regexp{
"css-accent-color": regexp.MustCompile(`(?i)(^|\s|;)accent-color(\s+)?:`),
"css-align-items": regexp.MustCompile(`(?i)(^|\s|;)align-items(\s+)?:`),
"css-aspect-ratio": regexp.MustCompile(`(?i)(^|\s|;)aspect-ratio(\s+)?:`),
"css-background-blend-mode": regexp.MustCompile(`(?i)(^|\s|;)background-blend-mode(\s+)?:`),
"css-background-clip": regexp.MustCompile(`(?i)(^|\s|;)background-clip(\s+)?:`),
"css-background-color": regexp.MustCompile(`(?i)(^|\s|;)background-color(\s+)?:`),
"css-background-image": regexp.MustCompile(`(?i)(^|\s|;)background-image(\s+)?:`),
"css-background-origin": regexp.MustCompile(`(?i)(^|\s|;)background-origin(\s+)?:`),
"css-background-position": regexp.MustCompile(`(?i)(^|\s|;)background-position(\s+)?:`),
"css-background-repeat": regexp.MustCompile(`(?i)(^|\s|;)background-repeat(\s+)?:`),
"css-background-size": regexp.MustCompile(`(?i)(^|\s|;)background-size(\s+)?:`),
"css-background": regexp.MustCompile(`(?i)(^|\s|;)background(\s+)?:`),
"css-block-inline-size": regexp.MustCompile(`(?i)(^|\s|;)block-inline-size(\s+)?:`),
"css-border-image": regexp.MustCompile(`(?i)(^|\s|;)border-image(\s+)?:`),
"css-border-inline-block-individual": regexp.MustCompile(`(?i)(^|\s|;)border-inline(\s+)?:`),
"css-border-radius": regexp.MustCompile(`(?i)(^|\s|;)border-radius(\s+)?:`),
"css-border": regexp.MustCompile(`(?i)(^|\s|;)border(\s+)?:`),
"css-box-shadow": regexp.MustCompile(`(?i)(^|\s|;)box-shadow(\s+)?:`),
"css-box-sizing": regexp.MustCompile(`(?i)(^|\s|;)box-sizing(\s+)?:`),
"css-caption-side": regexp.MustCompile(`(?i)(^|\s|;)caption-side(\s+)?:`),
"css-clip-path": regexp.MustCompile(`(?i)(^|\s|;)clip-path(\s+)?:`),
"css-column-count": regexp.MustCompile(`(?i)(^|\s|;)column-count(\s+)?:`),
"css-column-layout-properties": regexp.MustCompile(`(?i)(^|\s|;)column-layout-properties(\s+)?:`),
"css-conic-gradient": regexp.MustCompile(`(?i)(^|\s|;)conic-gradient(\s+)?:`),
"css-direction": regexp.MustCompile(`(?i)(^|\s|;)direction(\s+)?:`),
"css-display-flex": regexp.MustCompile(`(?i)(^|\s|;)display(\s+)?:(\s+)?flex($|\s|;)`),
"css-display-grid": regexp.MustCompile(`(?i)(^|\s|;)display:grid`),
"css-display-none": regexp.MustCompile(`(?i)(^|\s|;)display:none`),
"css-display": regexp.MustCompile(`(?i)(^|\s|;)display(\s+)?:`),
"css-filter": regexp.MustCompile(`(?i)(^|\s|;)filter(\s+)?:`),
"css-flex-direction": regexp.MustCompile(`(?i)(^|\s|;)flex-direction(\s+)?:`),
"css-flex-wrap": regexp.MustCompile(`(?i)(^|\s|;)flex-wrap(\s+)?:`),
"css-float": regexp.MustCompile(`(?i)(^|\s|;)float(\s+)?:`),
"css-font-kerning": regexp.MustCompile(`(?i)(^|\s|;)font-kerning(\s+)?:`),
"css-font-weight": regexp.MustCompile(`(?i)(^|\s|;)font-weight(\s+)?:`),
"css-font": regexp.MustCompile(`(?i)(^|\s|;)font(\s+)?:`),
"css-gap": regexp.MustCompile(`(?i)(^|\s|;)gap(\s+)?:`),
"css-grid-template": regexp.MustCompile(`(?i)(^|\s|;)grid-template(\s+)?:`),
"css-height": regexp.MustCompile(`(?i)(^|\s|;)height(\s+)?:`),
"css-hyphens": regexp.MustCompile(`(?i)(^|\s|;)hyphens(\s+)?:`),
"css-important": regexp.MustCompile(`(?i)!important($|\s|;)`),
"css-inline-size": regexp.MustCompile(`(?i)(^|\s|;)inline-size(\s+)?:`),
"css-intrinsic-size": regexp.MustCompile(`(?i)(^|\s|;)intrinsic-size(\s+)?:`),
"css-justify-content": regexp.MustCompile(`(?i)(^|\s|;)justify-content(\s+)?:`),
"css-letter-spacing": regexp.MustCompile(`(?i)(^|\s|;)letter-spacing(\s+)?:`),
"css-line-height": regexp.MustCompile(`(?i)(^|\s|;)line-height(\s+)?:`),
"css-list-style-image": regexp.MustCompile(`(?i)(^|\s|;)list-style-image(\s+)?:`),
"css-list-style-position": regexp.MustCompile(`(?i)(^|\s|;)list-style-position(\s+)?:`),
"css-list-style": regexp.MustCompile(`(?i)(^|\s|;)list-style(\s+)?:`),
"css-margin-block-start-end": regexp.MustCompile(`(?i)(^|\s|;)margin-block-(start|end)(\s+)?:`),
"css-margin-inline-block": regexp.MustCompile(`(?i)(^|\s|;)margin-inline-block(\s+)?:`),
"css-margin-inline-start-end": regexp.MustCompile(`(?i)(^|\s|;)margin-inline-(start|end)(\s+)?:`),
"css-margin-inline": regexp.MustCompile(`(?i)(^|\s|;)margin-inline(\s+)?:`),
"css-margin": regexp.MustCompile(`(?i)(^|\s|;)margin(\s+)?:`),
"css-max-block-size": regexp.MustCompile(`(?i)(^|\s|;)max-block-size(\s+)?:`),
"css-max-height": regexp.MustCompile(`(?i)(^|\s|;)max-height(\s+)?:`),
"css-max-width": regexp.MustCompile(`(?i)(^|\s|;)max-width(\s+)?:`),
"css-min-height": regexp.MustCompile(`(?i)(^|\s|;)min-height(\s+)?:`),
"css-min-inline-size": regexp.MustCompile(`(?i)(^|\s|;)min-inline-size(\s+)?:`),
"css-min-width": regexp.MustCompile(`(?i)(^|\s|;)min-width(\s+)?:`),
"css-mix-blend-mode": regexp.MustCompile(`(?i)(^|\s|;)mix-blend-mode(\s+)?:`),
"css-modern-color": regexp.MustCompile(`(?i)(^|\s|;)modern-color(\s+)?:`),
"css-object-fit": regexp.MustCompile(`(?i)(^|\s|;)object-fit(\s+)?:`),
"css-object-position": regexp.MustCompile(`(?i)(^|\s|;)object-position(\s+)?:`),
"css-opacity": regexp.MustCompile(`(?i)(^|\s|;)opacity(\s+)?:`),
"css-outline-offset": regexp.MustCompile(`(?i)(^|\s|;)outline-offset(\s+)?:`),
"css-outline": regexp.MustCompile(`(?i)(^|\s|;)outline(\s+)?:`),
"css-overflow-wrap": regexp.MustCompile(`(?i)(^|\s|;)overflow-wrap(\s+)?:`),
"css-overflow": regexp.MustCompile(`(?i)(^|\s|;)overflow(\s+)?:`),
"css-padding-block-start-end": regexp.MustCompile(`(?i)(^|\s|;)padding-block-(start|end)(\s+)?:`),
"css-padding-inline-block": regexp.MustCompile(`(?i)(^|\s|;)padding-inline-block(\s+)?:`),
"css-padding-inline-start-end": regexp.MustCompile(`(?i)(^|\s|;)padding-inline-(start|end)(\s+)?:`),
"css-padding": regexp.MustCompile(`(?i)(^|\s|;)padding(\s+)?:`),
"css-position": regexp.MustCompile(`(?i)(^|\s|;)position(\s+)?:`),
"css-radial-gradient": regexp.MustCompile(`(?i)(^|\s|;)radial-gradient(\s+)?:`),
"css-rgb": regexp.MustCompile(`(?i)(\s|:)rgb\(`),
"css-rgba": regexp.MustCompile(`(?i)(\s|:)rgba\(`),
"css-scroll-snap": regexp.MustCompile(`(?i)(^|\s|;)roll-snap(\s+)?:`),
"css-tab-size": regexp.MustCompile(`(?i)(^|\s|;)tab-size(\s+)?:`),
"css-table-layout": regexp.MustCompile(`(?i)(^|\s|;)table-layout(\s+)?:`),
"css-text-align-last": regexp.MustCompile(`(?i)(^|\s|;)text-align-last(\s+)?:`),
"css-text-align": regexp.MustCompile(`(?i)(^|\s|;)text-align(\s+)?:`),
"css-text-decoration-color": regexp.MustCompile(`(?i)(^|\s|;)text-decoration-color(\s+)?:`),
"css-text-decoration-thickness": regexp.MustCompile(`(?i)(^|\s|;)text-decoration-thickness(\s+)?:`),
"css-text-decoration": regexp.MustCompile(`(?i)(^|\s|;)text-decoration(\s+)?:`),
"css-text-emphasis-position": regexp.MustCompile(`(?i)(^|\s|;)text-emphasis-position(\s+)?:`),
"css-text-emphasis": regexp.MustCompile(`(?i)(^|\s|;)text-emphasis(\s+)?:`),
"css-text-indent": regexp.MustCompile(`(?i)(^|\s|;)text-indent(\s+)?:`),
"css-text-overflow": regexp.MustCompile(`(?i)(^|\s|;)text-overflow(\s+)?:`),
"css-text-shadow": regexp.MustCompile(`(?i)(^|\s|;)text-shadow(\s+)?:`),
"css-text-transform": regexp.MustCompile(`(?i)(^|\s|;)text-transform(\s+)?:`),
"css-text-underline-offset": regexp.MustCompile(`(?i)(^|\s|;)text-underline-offset(\s+)?:`),
"css-transform": regexp.MustCompile(`(?i)(^|\s|;)transform(\s+)?:`),
"css-unit-calc": regexp.MustCompile(`(?i)(\s|:)calc\(`),
"css-variables": regexp.MustCompile(`(?i)(^|\s|;)variables(\s+)?:`),
"css-visibility": regexp.MustCompile(`(?i)(^|\s|;)visibility(\s+)?:`),
"css-white-space": regexp.MustCompile(`(?i)(^|\s|;)white-space(\s+)?:`),
"css-width": regexp.MustCompile(`(?i)(^|\s|;)width(\s+)?:`),
"css-word-break": regexp.MustCompile(`(?i)(^|\s|;)word-break(\s+)?:`),
"css-writing-mode": regexp.MustCompile(`(?i)(^|\s|;)writing-mode(\s+)?:`),
"css-z-index": regexp.MustCompile(`(?i)(^|\s|;)z-index(\s+)?:`),
}
// some CSS tests using regex for things that can't be merged inline
var cssRegexpTests = map[string]*regexp.Regexp{
"css-at-font-face": regexp.MustCompile(`(?mi)@font\-face\s+?{`), // 26.923073
"css-at-import": regexp.MustCompile(`(?mi)@import\s`), // 36.170216
"css-at-keyframes": regexp.MustCompile(`(?mi)@keyframes\s`), // 31.914898
"css-at-media": regexp.MustCompile(`(?mi)@media\s?\(`), // 47.05882
"css-at-supports": regexp.MustCompile(`(?mi)@supports\s?\(`), // 40.81633
"css-pseudo-class-active": regexp.MustCompile(`:active`), // 52.173912
"css-pseudo-class-checked": regexp.MustCompile(`:checked`), // 31.91489
"css-pseudo-class-first-child": regexp.MustCompile(`:first\-child`), // 66.666664
"css-pseudo-class-first-of-type": regexp.MustCompile(`:first\-of\-type`), // 62.5
"css-pseudo-class-focus": regexp.MustCompile(`:focus`), // 47.826088
"css-pseudo-class-has": regexp.MustCompile(`:has`), // 25.531914
"css-pseudo-class-hover": regexp.MustCompile(`:hover`), // 60.41667
"css-pseudo-class-lang": regexp.MustCompile(`:lang\s?\(`), // 18.918922
"css-pseudo-class-last-child": regexp.MustCompile(`:last\-child`), // 64.58333
"css-pseudo-class-last-of-type": regexp.MustCompile(`:last\-of\-type`), // 60.416664
"css-pseudo-class-link": regexp.MustCompile(`:link`), // 81.63265
"css-pseudo-class-not": regexp.MustCompile(`:not(\s+)?\(`), // 44.89796
"css-pseudo-class-nth-child": regexp.MustCompile(`:nth\-child(\s+)?\(`), // 44.89796
"css-pseudo-class-nth-last-child": regexp.MustCompile(`:nth\-last\-child(\s+)?\(`), // 44.89796
"css-pseudo-class-nth-last-of-type": regexp.MustCompile(`:nth\-last\-of\-type(\s+)?\(`), // 42.857143
"css-pseudo-class-nth-of-type": regexp.MustCompile(`:nth\-of\-type(\s+)?\(`), // 42.857143
"css-pseudo-class-only-child": regexp.MustCompile(`:only\-child(\s+)?\(`), // 64.58333
"css-pseudo-class-only-of-type": regexp.MustCompile(`:only\-of\-type(\s+)?\(`), // 64.58333
"css-pseudo-class-target": regexp.MustCompile(`:target`), // 39.13044
"css-pseudo-class-visited": regexp.MustCompile(`:visited`), // 39.13044
"css-pseudo-element-after": regexp.MustCompile(`:after`), // 40
"css-pseudo-element-before": regexp.MustCompile(`:before`), // 40
"css-pseudo-element-first-letter": regexp.MustCompile(`::first\-letter`), // 60
"css-pseudo-element-first-line": regexp.MustCompile(`::first\-line`), // 60
"css-pseudo-element-marker": regexp.MustCompile(`::marker`), // 50
"css-pseudo-element-placeholder": regexp.MustCompile(`::placeholder`), // 32
"css-at-font-face": regexp.MustCompile(`(?mi)@font\-face\s+?{`),
"css-at-import": regexp.MustCompile(`(?mi)@import\s`),
"css-at-keyframes": regexp.MustCompile(`(?mi)@keyframes\s`),
"css-at-media": regexp.MustCompile(`(?mi)@media\s?\(`),
"css-at-supports": regexp.MustCompile(`(?mi)@supports\s?\(`),
"css-pseudo-class-active": regexp.MustCompile(`:active`),
"css-pseudo-class-checked": regexp.MustCompile(`:checked`),
"css-pseudo-class-first-child": regexp.MustCompile(`:first\-child`),
"css-pseudo-class-first-of-type": regexp.MustCompile(`:first\-of\-type`),
"css-pseudo-class-focus": regexp.MustCompile(`:focus`),
"css-pseudo-class-has": regexp.MustCompile(`:has`),
"css-pseudo-class-hover": regexp.MustCompile(`:hover`),
"css-pseudo-class-lang": regexp.MustCompile(`:lang\s?\(`),
"css-pseudo-class-last-child": regexp.MustCompile(`:last\-child`),
"css-pseudo-class-last-of-type": regexp.MustCompile(`:last\-of\-type`),
"css-pseudo-class-link": regexp.MustCompile(`:link`),
"css-pseudo-class-not": regexp.MustCompile(`:not(\s+)?\(`),
"css-pseudo-class-nth-child": regexp.MustCompile(`:nth\-child(\s+)?\(`),
"css-pseudo-class-nth-last-child": regexp.MustCompile(`:nth\-last\-child(\s+)?\(`),
"css-pseudo-class-nth-last-of-type": regexp.MustCompile(`:nth\-last\-of\-type(\s+)?\(`),
"css-pseudo-class-nth-of-type": regexp.MustCompile(`:nth\-of\-type(\s+)?\(`),
"css-pseudo-class-only-child": regexp.MustCompile(`:only\-child(\s+)?\(`),
"css-pseudo-class-only-of-type": regexp.MustCompile(`:only\-of\-type(\s+)?\(`),
"css-pseudo-class-target": regexp.MustCompile(`:target`),
"css-pseudo-class-visited": regexp.MustCompile(`:visited`),
"css-pseudo-element-after": regexp.MustCompile(`:after`),
"css-pseudo-element-before": regexp.MustCompile(`:before`),
"css-pseudo-element-first-letter": regexp.MustCompile(`::first\-letter`),
"css-pseudo-element-first-line": regexp.MustCompile(`::first\-line`),
"css-pseudo-element-marker": regexp.MustCompile(`::marker`),
"css-pseudo-element-placeholder": regexp.MustCompile(`::placeholder`),
}
// some CSS tests using regex for units
var cssRegexpUnitTests = map[string]*regexp.Regexp{
"css-unit-ch": regexp.MustCompile(`\b\d+ch\b`), // 66.666664
"css-unit-initial": regexp.MustCompile(`:\s?initial\b`), // 58.33333
"css-unit-rem": regexp.MustCompile(`\b\d+rem\b`), // 66.666664
"css-unit-vh": regexp.MustCompile(`\b\d+vh\b`), // 68.75
"css-unit-vmax": regexp.MustCompile(`\b\d+vmax\b`), // 60.416664
"css-unit-vmin": regexp.MustCompile(`\b\d+vmin\b`), // 58.333336
"css-unit-vw": regexp.MustCompile(`\b\d+vw\b`), // 77.08333
"css-unit-ch": regexp.MustCompile(`\b\d+ch\b`),
"css-unit-initial": regexp.MustCompile(`:\s?initial\b`),
"css-unit-rem": regexp.MustCompile(`\b\d+rem\b`),
"css-unit-vh": regexp.MustCompile(`\b\d+vh\b`),
"css-unit-vmax": regexp.MustCompile(`\b\d+vmax\b`),
"css-unit-vmin": regexp.MustCompile(`\b\d+vmin\b`),
"css-unit-vw": regexp.MustCompile(`\b\d+vw\b`),
}

View File

@@ -42,17 +42,15 @@ func runCSSTests(html string) ([]Warning, int, error) {
return results, totalTests, err
}
for key, test := range cssInlineTests {
totalTests++
found := len(doc.Find(test).Nodes)
if found > 0 {
inlineStyleResults := testInlineStyles(doc)
totalTests = totalTests + len(cssInlineRegexTests) + len(styleInlineAttributes)
for key, count := range inlineStyleResults {
result, err := cie.getTest(key)
if err != nil {
return results, totalTests, err
}
result.Score.Found = found
if err == nil {
result.Score.Found = count
results = append(results, result)
}
}
// get a list of all generated styles from all nodes
@@ -215,3 +213,39 @@ func isURL(str string) bool {
u, err := url.Parse(str)
return err == nil && (u.Scheme == "http" || u.Scheme == "https") && u.Host != ""
}
// Test the HTML for inline CSS styles and styling attributes
func testInlineStyles(doc *goquery.Document) map[string]int {
matches := make(map[string]int)
// find all elements containing a style attribute
styles := doc.Find("[style]").Nodes
for _, s := range styles {
style, err := tools.GetHTMLAttributeVal(s, "style")
if err != nil {
continue
}
for id, test := range cssInlineRegexTests {
if test.MatchString(style) {
if _, ok := matches[id]; !ok {
matches[id] = 0
}
matches[id]++
}
}
}
// find all elements containing styleInlineAttributes
for id, test := range styleInlineAttributes {
a := doc.Find(test).Nodes
if len(a) > 0 {
if _, ok := matches[id]; !ok {
matches[id] = 0
}
matches[id]++
}
}
return matches
}

View File

@@ -0,0 +1,81 @@
package htmlcheck
import (
"fmt"
"sort"
"strings"
"testing"
"github.com/PuerkitoBio/goquery"
)
func TestInlineStyleDetection(t *testing.T) {
/// tests should contain the HTML test, and expected test results in alphabetical order
tests := map[string]string{}
tests[`<h1 style="transform: rotate(20deg)">Heading</h1>`] = "css-transform"
tests[`<h1 style="color: green; transform:rotate(20deg)">Heading</h1>`] = "css-transform"
tests[`<h1 style="color:green; transform :rotate(20deg)">Heading</h1>`] = "css-transform"
tests[`<h1 style="transform:rotate(20deg)">Heading</h1>`] = "css-transform"
tests[`<h1 style="TRANSFORM:rotate(20deg)">Heading</h1>`] = "css-transform"
tests[`<h1 style="transform: rotate(20deg)">Heading</h1>`] = "css-transform"
tests[`<h1 style="ignore-transform: something">Heading</h1>`] = "" // no match
tests[`<h1 style="text-transform: uppercase">Heading</h1>`] = "css-text-transform"
tests[`<h1 style="text-transform: uppercase; text-transform: uppercase">Heading</h1>`] = "css-text-transform"
tests[`<h1 style="test-transform: uppercase">Heading</h1>`] = "" // no match
tests[`<h1 style="padding-inline-start: 5rem">Heading</h1>`] = "css-padding-inline-start-end"
tests[`<h1 style="margin-inline-end: 5rem">Heading</h1>`] = "css-margin-inline-start-end"
tests[`<h1 style="margin-inline-middle: 5rem">Heading</h1>`] = "" // no match
tests[`<h1 style="color:green!important">Heading</h1>`] = "css-important"
tests[`<h1 style="color: green !important">Heading</h1>`] = "css-important"
tests[`<h1 style="color: green!important;">Heading</h1>`] = "css-important"
tests[`<h1 style="color:green!important-stuff;">Heading</h1>`] = "" // no match
tests[`<h1 style="background-image:url('img.jpg')">Heading</h1>`] = "css-background-image"
tests[`<h1 style="background-image:url('img.jpg'); color: green">Heading</h1>`] = "css-background-image"
tests[`<h1 style=" color:green; background-image:url('img.jpg');">Heading</h1>`] = "css-background-image"
tests[`<h1 style="display : flex ;">Heading</h1>`] = "css-display,css-display-flex"
tests[`<h1 style="DISPLAY:FLEX;">Heading</h1>`] = "css-display,css-display-flex"
tests[`<h1 style="display: flexing;">Heading</h1>`] = "css-display" // should not match css-display-flex rule
tests[`<h1 style="line-height: 1rem;opacity: 0.5; width: calc(10px + 100px)">Heading</h1>`] = "css-line-height,css-opacity,css-unit-calc,css-width"
tests[`<h1 style="color: rgb(255,255,255);">Heading</h1>`] = "css-rgb"
tests[`<h1 style="color:rgb(255,255,255);">Heading</h1>`] = "css-rgb"
tests[`<h1 style="color:rgb(255,255,255);">Heading</h1>`] = "css-rgb"
tests[`<h1 style="color:rgba(255,255,255, 0);">Heading</h1>`] = "css-rgba"
tests[`<h1 style="border: solid rgb(255,255,255) 1px; color:rgba(255,255,255, 0);">Heading</h1>`] = "css-border,css-rgb,css-rgba"
tests[`<h1 border="2">Heading</h1>`] = "css-border"
tests[`<h1 border="2" background="green">Heading</h1>`] = "css-background,css-border"
tests[`<h1 BORDER="2" BACKGROUND="GREEN">Heading</h1>`] = "css-background,css-border"
tests[`<h1 border-something="2" background-something="green">Heading</h1>`] = "" // no match
tests[`<h1 border="2" style="border: solid green 1px!important">Heading</h1>`] = "css-border,css-important"
for html, expected := range tests {
reader := strings.NewReader(html)
doc, err := goquery.NewDocumentFromReader(reader)
if err != nil {
t.Log("error ", err)
t.Fail()
}
results := testInlineStyles(doc)
matches := []string{}
uniqMap := make(map[string]bool)
for key := range results {
if _, exists := uniqMap[key]; !exists {
matches = append(matches, key)
}
}
// ensure results are sorted to ensure consistent results
sort.Strings(matches)
assertEqual(t, expected, strings.Join(matches, ","), fmt.Sprintf("inline style detection \"%s\"", html))
}
}
func assertEqual(t *testing.T, a interface{}, b interface{}, message string) {
if a == b {
return
}
message = fmt.Sprintf("%s: \"%v\" != \"%v\"", message, a, b)
t.Fatal(message)
}

View File

@@ -56,14 +56,14 @@ func SaveToDatabase(origin net.Addr, from string, to []string, data []byte) (str
}
}
messageID := strings.Trim(msg.Header.Get("Message-Id"), "<>")
messageID := strings.Trim(msg.Header.Get("Message-ID"), "<>")
// add a message ID if not set
if messageID == "" {
// generate unique ID
messageID = shortuuid.New() + "@mailpit"
// add unique ID
data = append([]byte("Message-Id: <"+messageID+">\r\n"), data...)
data = append([]byte("Message-ID: <"+messageID+">\r\n"), data...)
} else if config.IgnoreDuplicateIDs {
if storage.MessageIDExists(messageID) {
logger.Log().Debugf("[smtpd] duplicate message found, ignoring %s", messageID)

614
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -178,8 +178,8 @@ func ReleaseMessage(w http.ResponseWriter, r *http.Request) {
// generate unique ID
uid := shortuuid.New() + "@mailpit"
// update Message-Id with unique ID
msg, err = tools.SetMessageHeader(msg, "Message-Id", "<"+uid+">")
// update Message-ID with unique ID
msg, err = tools.SetMessageHeader(msg, "Message-ID", "<"+uid+">")
if err != nil {
httpError(w, err.Error())
return