mirror of
https://github.com/jhillyerd/inbucket.git
synced 2025-12-20 11:07:01 +00:00
@@ -5,13 +5,11 @@ env:
|
|||||||
- DEPLOY_WITH_MAJOR="1.9"
|
- DEPLOY_WITH_MAJOR="1.9"
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- go vet ./...
|
- go get github.com/golang/lint/golint
|
||||||
|
|
||||||
go:
|
go:
|
||||||
- 1.8.x
|
|
||||||
- 1.9.x
|
- 1.9.x
|
||||||
|
- "1.10"
|
||||||
script: go test -race -v ./...
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: script
|
provider: script
|
||||||
|
|||||||
30
CHANGELOG.md
30
CHANGELOG.md
@@ -4,8 +4,16 @@ Change Log
|
|||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
[1.2.0-rc2] - 2017-12-15
|
## [Unreleased]
|
||||||
------------------------
|
|
||||||
|
### Added
|
||||||
|
- Button to purge mailbox contents from the UI.
|
||||||
|
- Simple HTML/CSS sanitization; `Safe HTML` and `Plain Text` UI tabs.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- Reverse message display sort order in the UI; now newest first.
|
||||||
|
|
||||||
|
## [1.2.0-rc2] - 2017-12-15
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- `rest/client` types `MessageHeader` and `Message` with convenience methods;
|
- `rest/client` types `MessageHeader` and `Message` with convenience methods;
|
||||||
@@ -20,8 +28,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
types
|
types
|
||||||
- Fixed panic when `monitor.history` set to 0
|
- Fixed panic when `monitor.history` set to 0
|
||||||
|
|
||||||
[1.2.0-rc1] - 2017-01-29
|
## [1.2.0-rc1] - 2017-01-29
|
||||||
------------------------
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Storage of `To:` header in messages (likely breaks existing datastores)
|
- Storage of `To:` header in messages (likely breaks existing datastores)
|
||||||
@@ -47,8 +54,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Allow increased local-part length of 128 chars for Mailgun
|
- Allow increased local-part length of 128 chars for Mailgun
|
||||||
- RedHat and Ubuntu now use systemd instead of legacy init systems
|
- RedHat and Ubuntu now use systemd instead of legacy init systems
|
||||||
|
|
||||||
[1.1.0] - 2016-09-03
|
## [1.1.0] - 2016-09-03
|
||||||
--------------------
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Homebrew inbucket.conf and formula (see README)
|
- Homebrew inbucket.conf and formula (see README)
|
||||||
@@ -56,8 +62,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
### Fixed
|
### Fixed
|
||||||
- Log and continue when unable to delete oldest message during cap enforcement
|
- Log and continue when unable to delete oldest message during cap enforcement
|
||||||
|
|
||||||
[1.1.0-rc2] - 2016-03-06
|
## [1.1.0-rc2] - 2016-03-06
|
||||||
------------------------
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Message Cap to status page
|
- Message Cap to status page
|
||||||
@@ -67,8 +72,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- Shutdown hang in retention scanner
|
- Shutdown hang in retention scanner
|
||||||
- Display empty subject as `(No Subject)`
|
- Display empty subject as `(No Subject)`
|
||||||
|
|
||||||
[1.1.0-rc1] - 2016-03-04
|
## [1.1.0-rc1] - 2016-03-04
|
||||||
------------------------
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Inbucket now builds with Go 1.5 or 1.6
|
- Inbucket now builds with Go 1.5 or 1.6
|
||||||
@@ -82,8 +86,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
- RESTful API moved to `/api/v1` base URI
|
- RESTful API moved to `/api/v1` base URI
|
||||||
- More graceful shutdown on Ctrl-C or when errors encountered
|
- More graceful shutdown on Ctrl-C or when errors encountered
|
||||||
|
|
||||||
[1.0] - 2014-04-14
|
## [1.0] - 2014-04-14
|
||||||
------------------
|
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Add new configuration option `mailbox.message.cap` to prevent individual
|
- Add new configuration option `mailbox.message.cap` to prevent individual
|
||||||
@@ -100,8 +103,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|||||||
[1.0]: https://github.com/jhillyerd/inbucket/compare/1.0-rc1...1.0
|
[1.0]: https://github.com/jhillyerd/inbucket/compare/1.0-rc1...1.0
|
||||||
|
|
||||||
|
|
||||||
Release Checklist
|
## Release Checklist
|
||||||
-----------------
|
|
||||||
|
|
||||||
1. Create release branch: `git flow release start 1.x.0`
|
1. Create release branch: `git flow release start 1.x.0`
|
||||||
2. Update CHANGELOG.md:
|
2. Update CHANGELOG.md:
|
||||||
|
|||||||
35
Makefile
Normal file
35
Makefile
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
PKG := inbucket
|
||||||
|
SHELL := /bin/sh
|
||||||
|
|
||||||
|
SRC := $(shell find . -type f -name '*.go' -not -path "./vendor/*")
|
||||||
|
PKGS := $$(go list ./... | grep -v /vendor/)
|
||||||
|
|
||||||
|
.PHONY: all build clean fmt install lint simplify test
|
||||||
|
|
||||||
|
all: test lint build
|
||||||
|
|
||||||
|
clean:
|
||||||
|
go clean
|
||||||
|
|
||||||
|
deps:
|
||||||
|
go get -t ./...
|
||||||
|
|
||||||
|
build: clean deps
|
||||||
|
go build
|
||||||
|
|
||||||
|
install: build
|
||||||
|
go install
|
||||||
|
|
||||||
|
test: clean deps
|
||||||
|
go test -race ./...
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
@gofmt -l -w $(SRC)
|
||||||
|
|
||||||
|
simplify:
|
||||||
|
@gofmt -s -l -w $(SRC)
|
||||||
|
|
||||||
|
lint:
|
||||||
|
@test -z "$(shell gofmt -l . | tee /dev/stderr)" || echo "[WARN] Fix formatting issues with 'make fmt'"
|
||||||
|
@golint -set_exit_status $${PKGS}
|
||||||
|
@go vet $${PKGS}
|
||||||
@@ -86,7 +86,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup signal handler
|
// Setup signal handler
|
||||||
sigChan := make(chan os.Signal)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
|
signal.Notify(sigChan, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT)
|
||||||
|
|
||||||
// Initialize logging
|
// Initialize logging
|
||||||
@@ -150,7 +150,7 @@ signalLoop:
|
|||||||
log.Infof("Received SIGTERM, shutting down")
|
log.Infof("Received SIGTERM, shutting down")
|
||||||
close(shutdownChan)
|
close(shutdownChan)
|
||||||
}
|
}
|
||||||
case _ = <-shutdownChan:
|
case <-shutdownChan:
|
||||||
rootCancel()
|
rootCancel()
|
||||||
break signalLoop
|
break signalLoop
|
||||||
}
|
}
|
||||||
|
|||||||
110
sanitize/css.go
Normal file
110
sanitize/css.go
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package sanitize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gorilla/css/scanner"
|
||||||
|
)
|
||||||
|
|
||||||
|
// propertyRule may someday allow control of what values are valid for a particular property.
|
||||||
|
type propertyRule struct{}
|
||||||
|
|
||||||
|
var allowedProperties = map[string]propertyRule{
|
||||||
|
"align": {},
|
||||||
|
"background-color": {},
|
||||||
|
"border": {},
|
||||||
|
"border-bottom": {},
|
||||||
|
"border-left": {},
|
||||||
|
"border-radius": {},
|
||||||
|
"border-right": {},
|
||||||
|
"border-top": {},
|
||||||
|
"box-sizing": {},
|
||||||
|
"clear": {},
|
||||||
|
"color": {},
|
||||||
|
"content": {},
|
||||||
|
"display": {},
|
||||||
|
"font-family": {},
|
||||||
|
"font-size": {},
|
||||||
|
"font-weight": {},
|
||||||
|
"height": {},
|
||||||
|
"line-height": {},
|
||||||
|
"margin": {},
|
||||||
|
"margin-bottom": {},
|
||||||
|
"margin-left": {},
|
||||||
|
"margin-right": {},
|
||||||
|
"margin-top": {},
|
||||||
|
"max-height": {},
|
||||||
|
"max-width": {},
|
||||||
|
"overflow": {},
|
||||||
|
"padding": {},
|
||||||
|
"padding-bottom": {},
|
||||||
|
"padding-left": {},
|
||||||
|
"padding-right": {},
|
||||||
|
"padding-top": {},
|
||||||
|
"table-layout": {},
|
||||||
|
"text-align": {},
|
||||||
|
"text-decoration": {},
|
||||||
|
"text-shadow": {},
|
||||||
|
"vertical-align": {},
|
||||||
|
"width": {},
|
||||||
|
"word-break": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler Token, return next state.
|
||||||
|
type stateHandler func(b *bytes.Buffer, t *scanner.Token) stateHandler
|
||||||
|
|
||||||
|
func sanitizeStyle(input string) string {
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
scan := scanner.New(input)
|
||||||
|
state := stateStart
|
||||||
|
for {
|
||||||
|
t := scan.Next()
|
||||||
|
if t.Type == scanner.TokenEOF {
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
if t.Type == scanner.TokenError {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
state = state(b, t)
|
||||||
|
if state == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateStart(b *bytes.Buffer, t *scanner.Token) stateHandler {
|
||||||
|
switch t.Type {
|
||||||
|
case scanner.TokenIdent:
|
||||||
|
_, ok := allowedProperties[strings.ToLower(t.Value)]
|
||||||
|
if !ok {
|
||||||
|
return stateEat
|
||||||
|
}
|
||||||
|
b.WriteString(t.Value)
|
||||||
|
return stateValid
|
||||||
|
case scanner.TokenS:
|
||||||
|
return stateStart
|
||||||
|
}
|
||||||
|
// Unexpected type.
|
||||||
|
b.WriteString("/*" + t.Type.String() + "*/")
|
||||||
|
return stateEat
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateEat(b *bytes.Buffer, t *scanner.Token) stateHandler {
|
||||||
|
if t.Type == scanner.TokenChar && t.Value == ";" {
|
||||||
|
// Done eating.
|
||||||
|
return stateStart
|
||||||
|
}
|
||||||
|
// Throw away this token.
|
||||||
|
return stateEat
|
||||||
|
}
|
||||||
|
|
||||||
|
func stateValid(b *bytes.Buffer, t *scanner.Token) stateHandler {
|
||||||
|
state := stateValid
|
||||||
|
if t.Type == scanner.TokenChar && t.Value == ";" {
|
||||||
|
// End of property.
|
||||||
|
state = stateStart
|
||||||
|
}
|
||||||
|
b.WriteString(t.Value)
|
||||||
|
return state
|
||||||
|
}
|
||||||
34
sanitize/css_test.go
Normal file
34
sanitize/css_test.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package sanitize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSanitizeStyle(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
input, want string
|
||||||
|
}{
|
||||||
|
{"", ""},
|
||||||
|
{
|
||||||
|
"color: red;",
|
||||||
|
"color: red;",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background-color: black; color: white",
|
||||||
|
"background-color: black;color: white",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"background-color: black; invalid: true; color: white",
|
||||||
|
"background-color: black;color: white",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.input, func(t *testing.T) {
|
||||||
|
got := sanitizeStyle(tc.input)
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("got: %q, want: %q, input: %q", got, tc.want, tc.input)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
88
sanitize/html.go
Normal file
88
sanitize/html.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package sanitize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/microcosm-cc/bluemonday"
|
||||||
|
"golang.org/x/net/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
cssSafe = regexp.MustCompile(".*")
|
||||||
|
policy = bluemonday.UGCPolicy().
|
||||||
|
AllowElements("center").
|
||||||
|
AllowAttrs("style").Matching(cssSafe).Globally()
|
||||||
|
)
|
||||||
|
|
||||||
|
func HTML(html string) (output string, err error) {
|
||||||
|
output, err = sanitizeStyleTags(html)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
output = policy.Sanitize(output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func sanitizeStyleTags(input string) (string, error) {
|
||||||
|
r := strings.NewReader(input)
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
if err := styleTagFilter(b, r); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return b.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func styleTagFilter(w io.Writer, r io.Reader) error {
|
||||||
|
bw := bufio.NewWriter(w)
|
||||||
|
b := make([]byte, 256)
|
||||||
|
z := html.NewTokenizer(r)
|
||||||
|
for {
|
||||||
|
b = b[:0]
|
||||||
|
tt := z.Next()
|
||||||
|
switch tt {
|
||||||
|
case html.ErrorToken:
|
||||||
|
err := z.Err()
|
||||||
|
if err == io.EOF {
|
||||||
|
return bw.Flush()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
case html.StartTagToken, html.SelfClosingTagToken:
|
||||||
|
name, hasAttr := z.TagName()
|
||||||
|
if !hasAttr {
|
||||||
|
bw.Write(z.Raw())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b = append(b, '<')
|
||||||
|
b = append(b, name...)
|
||||||
|
for {
|
||||||
|
key, val, more := z.TagAttr()
|
||||||
|
strval := string(val)
|
||||||
|
style := false
|
||||||
|
if strings.ToLower(string(key)) == "style" {
|
||||||
|
style = true
|
||||||
|
strval = sanitizeStyle(strval)
|
||||||
|
}
|
||||||
|
if !style || strval != "" {
|
||||||
|
b = append(b, ' ')
|
||||||
|
b = append(b, key...)
|
||||||
|
b = append(b, '=', '"')
|
||||||
|
b = append(b, []byte(html.EscapeString(strval))...)
|
||||||
|
b = append(b, '"')
|
||||||
|
}
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if tt == html.SelfClosingTagToken {
|
||||||
|
b = append(b, '/')
|
||||||
|
}
|
||||||
|
bw.Write(append(b, '>'))
|
||||||
|
default:
|
||||||
|
bw.Write(z.Raw())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
171
sanitize/html_test.go
Normal file
171
sanitize/html_test.go
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
package sanitize_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/jhillyerd/inbucket/sanitize"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestHTMLPlainStrings test plain text passthrough
|
||||||
|
func TestHTMLPlainStrings(t *testing.T) {
|
||||||
|
testStrings := []string{
|
||||||
|
"",
|
||||||
|
"plain string",
|
||||||
|
"one < two",
|
||||||
|
}
|
||||||
|
for _, ts := range testStrings {
|
||||||
|
t.Run(ts, func(t *testing.T) {
|
||||||
|
got, err := sanitize.HTML(ts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != ts {
|
||||||
|
t.Errorf("Got: %q, want: %q", got, ts)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHTMLSimpleFormatting tests basic tags we should allow
|
||||||
|
func TestHTMLSimpleFormatting(t *testing.T) {
|
||||||
|
testStrings := []string{
|
||||||
|
"<p>paragraph</p>",
|
||||||
|
"<b>bold</b>",
|
||||||
|
"<i>italic</b>",
|
||||||
|
"<em>emphasis</em>",
|
||||||
|
"<strong>strong</strong>",
|
||||||
|
"<div><span>text</span></div>",
|
||||||
|
"<center>text</center>",
|
||||||
|
}
|
||||||
|
for _, ts := range testStrings {
|
||||||
|
t.Run(ts, func(t *testing.T) {
|
||||||
|
got, err := sanitize.HTML(ts)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != ts {
|
||||||
|
t.Errorf("Got: %q, want: %q", got, ts)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestHTMLScriptTags tests some strings with JavaScript
|
||||||
|
func TestHTMLScriptTags(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
input, want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
`safe<script>nope</script>`,
|
||||||
|
`safe`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`<a onblur="alert(something)" href="http://mysite.com">mysite</a>`,
|
||||||
|
`<a href="http://mysite.com" rel="nofollow">mysite</a>`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.input, func(t *testing.T) {
|
||||||
|
got, err := sanitize.HTML(tc.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("Got: %q, want: %q", got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSanitizeStyleTags(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name, input, want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"empty",
|
||||||
|
``,
|
||||||
|
``,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open",
|
||||||
|
`<div>`,
|
||||||
|
`<div>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open close",
|
||||||
|
`<div></div>`,
|
||||||
|
`<div></div>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inner text",
|
||||||
|
`<div>foo bar</div>`,
|
||||||
|
`<div>foo bar</div>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"self close",
|
||||||
|
`<br/>`,
|
||||||
|
`<br/>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open params",
|
||||||
|
`<div id="me">`,
|
||||||
|
`<div id="me">`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open params squote",
|
||||||
|
`<div id="me" title='best'>`,
|
||||||
|
`<div id="me" title="best">`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open style",
|
||||||
|
`<div id="me" style="color: red;">`,
|
||||||
|
`<div id="me" style="color: red;">`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open style squote",
|
||||||
|
`<div id="me" style='color: red;'>`,
|
||||||
|
`<div id="me" style="color: red;">`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"open style mixed case",
|
||||||
|
`<div id="me" StYlE="color: red;">`,
|
||||||
|
`<div id="me" style="color: red;">`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"closed style",
|
||||||
|
`<br style="border: 1px solid red;"/>`,
|
||||||
|
`<br style="border: 1px solid red;"/>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mixed case style",
|
||||||
|
`<br StYlE="border: 1px solid red;"/>`,
|
||||||
|
`<br style="border: 1px solid red;"/>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mixed case invalid style",
|
||||||
|
`<br StYlE="position: fixed;"/>`,
|
||||||
|
`<br/>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mixed",
|
||||||
|
`<p id='i' title="cla'zz" style="font-size: 25px;"><b>some text</b></p>`,
|
||||||
|
`<p id="i" title="cla'zz" style="font-size: 25px;"><b>some text</b></p>`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"invalid styles",
|
||||||
|
`<div id="me" style='position: absolute;'>`,
|
||||||
|
`<div id="me">`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
got, err := sanitize.HTML(tc.input)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if got != tc.want {
|
||||||
|
t.Errorf("input: %s\ngot : %s\nwant: %s", tc.input, got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
393
swaks-tests/nonmime-html-inlined.raw
Normal file
393
swaks-tests/nonmime-html-inlined.raw
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
Date: %DATE%
|
||||||
|
To: %TO_ADDRESS%
|
||||||
|
From: %FROM_ADDRESS%
|
||||||
|
Subject: tutsplus responsive inlined CSS
|
||||||
|
MIME-Version: 1.0
|
||||||
|
Content-Type: text/html; charset="UTF-8"
|
||||||
|
|
||||||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<!--[if !mso]><!-->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<!--<![endif]-->
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title></title>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
<style type="text/css">
|
||||||
|
table {border-collapse: collapse !important;}
|
||||||
|
</style>
|
||||||
|
<![endif]-->
|
||||||
|
</head>
|
||||||
|
<body style="margin-top:0 !important;margin-bottom:0 !important;margin-right:0 !important;margin-left:0 !important;padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;background-color:#ffffff;" >
|
||||||
|
<center class="wrapper" style="width:100%;table-layout:fixed;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;" >
|
||||||
|
<div class="webkit" style="max-width:600px;margin-top:0;margin-bottom:0;margin-right:auto;margin-left:auto;" >
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
<table width="600" align="center" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<table class="outer" align="center" style="border-spacing:0;font-family:sans-serif;color:#333333;Margin:0 auto;width:100%;max-width:600px;" >
|
||||||
|
<tr>
|
||||||
|
<td class="full-width-image" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<img src="http://www.inbucket.org/email-assets/responsive/header.jpg" width="600" alt="" style="border-width:0;width:100%;max-width:600px;height:auto;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="one-column" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;text-align:left;" >
|
||||||
|
<p class="h1" style="Margin:0;font-weight:bold;font-size:14px;Margin-bottom:10px;" >Lorem ipsum dolor sit amet</p>
|
||||||
|
<p style="Margin:0;font-size:14px;Margin-bottom:10px;" >
|
||||||
|
Compare to:
|
||||||
|
<a href="http://tutsplus.github.io/creating-a-future-proof-responsive-email-without-media-queries/index.html" style="color:#ee6a56;text-decoration:underline;" >
|
||||||
|
tutsplus sample</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p style="Margin:0;font-size:14px;Margin-bottom:10px;" >Copyright (c) 2015, Envato Tuts+<br/>
|
||||||
|
All rights reserved.</p>
|
||||||
|
|
||||||
|
<p style="Margin:0;font-size:14px;Margin-bottom:10px;" >Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:</p>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.</li>
|
||||||
|
|
||||||
|
<li>Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p style="Margin:0;font-size:14px;Margin-bottom:10px;" >THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="two-column" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;text-align:center;font-size:0;" >
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td width="50%" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:300px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;" >
|
||||||
|
<table class="contents" style="border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:left;" >
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<img src="http://www.inbucket.org/email-assets/responsive/two-column-01.jpg" width="280" alt="" style="border-width:0;width:100%;max-width:280px;height:auto;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text" style="padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;" >
|
||||||
|
Maecenas sed ante pellentesque, posuere leo id, eleifend dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td><td width="50%" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:300px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;" >
|
||||||
|
<table class="contents" style="border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:left;" >
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<img src="http://www.inbucket.org/email-assets/responsive/two-column-02.jpg" width="280" alt="" style="border-width:0;width:100%;max-width:280px;height:auto;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text" style="padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;" >
|
||||||
|
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Maecenas sed ante pellentesque, posuere leo id, eleifend dolor.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="three-column" style="padding-right:0;padding-left:0;text-align:center;font-size:0;padding-top:10px;padding-bottom:10px;" >
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td width="200" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:200px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;" >
|
||||||
|
<table class="contents" style="border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<img src="http://www.inbucket.org/email-assets/responsive/three-column-01.jpg" width="180" alt="" style="border-width:0;width:100%;max-width:180px;height:auto;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text" style="padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;" >
|
||||||
|
Scelerisque congue eros eu posuere. Praesent in felis ut velit pretium lobortis rhoncus ut erat.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td><td width="200" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:200px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;" >
|
||||||
|
<table class="contents" style="border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<img src="http://www.inbucket.org/email-assets/responsive/three-column-02.jpg" width="180" alt="" style="border-width:0;width:100%;max-width:180px;height:auto;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text" style="padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;" >
|
||||||
|
Maecenas sed ante pellentesque, posuere leo id, eleifend dolor.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td><td width="200" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:200px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;" >
|
||||||
|
<table class="contents" style="border-spacing:0;font-family:sans-serif;color:#333333;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<tr>
|
||||||
|
<td style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<img src="http://www.inbucket.org/email-assets/responsive/three-column-03.jpg" width="180" alt="" style="border-width:0;width:100%;max-width:180px;height:auto;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="text" style="padding-bottom:0;padding-right:0;padding-left:0;padding-top:10px;" >
|
||||||
|
Praesent laoreet malesuada cursus. Maecenas scelerisque congue eros eu posuere.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="three-column" style="padding-right:0;padding-left:0;text-align:center;font-size:0;padding-top:10px;padding-bottom:10px;" >
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td width="200" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:200px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<p class="h2" style="Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;" >Fashion</p>
|
||||||
|
<p style="Margin:0;" >Class eleifend aptent taciti sociosqu ad litora torquent conubia</p>
|
||||||
|
<p style="Margin:0;" ><a href="#" style="color:#ee6a56;text-decoration:underline;" >Read requirements</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td><td width="200" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:200px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<p class="h2" style="Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;" >Photography</p>
|
||||||
|
<p style="Margin:0;" >Maecenas sed ante pellentesque, posuere leo id, eleifend dolor</p>
|
||||||
|
<p style="Margin:0;" ><a href="#" style="color:#ee6a56;text-decoration:underline;" >See examples</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td><td width="200" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:200px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<p class="h2" style="Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;" >Design</p>
|
||||||
|
<p style="Margin:0;" >Class aptent taciti sociosqu eleifend ad litora per conubia nostra</p>
|
||||||
|
<p style="Margin:0;" ><a href="#" style="color:#ee6a56;text-decoration:underline;" >See the winners</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td width="200" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:200px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<p class="h2" style="Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;" >Cooking</p>
|
||||||
|
<p style="Margin:0;" >Class aptent taciti eleifend sociosqu ad litora torquent conubia</p>
|
||||||
|
<p style="Margin:0;" ><a href="#" style="color:#ee6a56;text-decoration:underline;" >Read recipes</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td><td width="200" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:200px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<p class="h2" style="Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;" >Woodworking</p>
|
||||||
|
<p style="Margin:0;" >Maecenas sed ante pellentesque, posuere leo id, eleifend dolor</p>
|
||||||
|
<p style="Margin:0;" ><a href="#" style="color:#ee6a56;text-decoration:underline;" >See examples</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td><td width="200" valign="top" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column" style="width:100%;max-width:200px;display:inline-block;vertical-align:top;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<p class="h2" style="Margin:0;font-size:18px;font-weight:bold;Margin-bottom:12px;" >Craft</p>
|
||||||
|
<p style="Margin:0;" >Class aptent taciti sociosqu ad eleifend litora per conubia nostra</p>
|
||||||
|
<p style="Margin:0;" ><a href="#" style="color:#ee6a56;text-decoration:underline;" >Vote now</a></p>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="left-sidebar" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;text-align:center;font-size:0;" >
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td width="100" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column left" style="width:100%;display:inline-block;vertical-align:middle;max-width:100px;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;" >
|
||||||
|
<img src="http://www.inbucket.org/email-assets/responsive/sidebar-01.jpg" width="80" alt="" style="border-width:0;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td><td width="500" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column right" style="width:100%;display:inline-block;vertical-align:middle;max-width:500px;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
Praesent laoreet malesuada cursus. Maecenas scelerisque congue eros eu posuere. Praesent in felis ut velit pretium lobortis rhoncus ut erat. <a href="#" style="text-decoration:underline;color:#85ab70;" >Read on</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="right-sidebar" dir="rtl" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;text-align:center;font-size:0;" >
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
<table width="100%" dir="rtl" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td width="100" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column left" dir="ltr" style="width:100%;display:inline-block;vertical-align:middle;max-width:100px;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
<img src="http://www.inbucket.org/email-assets/responsive/sidebar-02.jpg" width="80" alt="" style="border-width:0;" />
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td><td width="500" style="padding-top:0;padding-bottom:0;padding-right:0;padding-left:0;" >
|
||||||
|
<![endif]-->
|
||||||
|
<div class="column right" dir="ltr" style="width:100%;display:inline-block;vertical-align:middle;max-width:500px;" >
|
||||||
|
<table width="100%" style="border-spacing:0;font-family:sans-serif;color:#333333;" >
|
||||||
|
<tr>
|
||||||
|
<td class="inner contents" style="padding-top:10px;padding-bottom:10px;padding-right:10px;padding-left:10px;width:100%;font-size:14px;text-align:center;" >
|
||||||
|
Maecenas sed ante pellentesque, posuere leo id, eleifend dolor. Class aptent taciti sociosqu ad litora torquent per conubia nostra. <a href="#" style="text-decoration:underline;color:#70bbd9;" >Per inceptos</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<!--[if (gte mso 9)|(IE)]>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<![endif]-->
|
||||||
|
</div>
|
||||||
|
</center>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
Date: %DATE%
|
Date: %DATE%
|
||||||
To: %TO_ADDRESS%
|
To: %TO_ADDRESS%
|
||||||
From: %FROM_ADDRESS%
|
From: %FROM_ADDRESS%
|
||||||
Subject: tutsplus responsive
|
Subject: tutsplus responsive external CSS
|
||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/html; charset="UTF-8"
|
Content-Type: text/html; charset="UTF-8"
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,6 @@ Subject: Swaks HTML
|
|||||||
MIME-Version: 1.0
|
MIME-Version: 1.0
|
||||||
Content-Type: text/html; charset="UTF-8"
|
Content-Type: text/html; charset="UTF-8"
|
||||||
|
|
||||||
This is a test of <b>HTML</b> at the <i>top</i> level.
|
<p style="font-family: 'Courier New', Courier, monospace;">
|
||||||
|
This is a test of <b>HTML</b> at the <i>top</i> level.
|
||||||
|
</p>
|
||||||
|
|||||||
@@ -53,5 +53,6 @@ swaks $* --data gmail.raw
|
|||||||
# Outlook test
|
# Outlook test
|
||||||
swaks $* --data outlook.raw
|
swaks $* --data outlook.raw
|
||||||
|
|
||||||
# Nonemime responsive HTML test
|
# Non-mime responsive HTML test
|
||||||
swaks $* --data nonmime-html-responsive.raw
|
swaks $* --data nonmime-html-responsive.raw
|
||||||
|
swaks $* --data nonmime-html-inlined.raw
|
||||||
|
|||||||
@@ -51,12 +51,17 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#body-tabs > li > a {
|
||||||
|
padding-top: 6px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
.message-body {
|
.message-body {
|
||||||
padding: 0 5px;
|
padding: 10px 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-attachments {
|
.message-attachments {
|
||||||
margin-top: 20px;
|
margin-top: 5px;
|
||||||
padding: 10px 10px 0 0;
|
padding: 10px 10px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -157,6 +157,7 @@ function onMessageLoaded(responseText, textStatus, XMLHttpRequest) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onDocumentChange();
|
onDocumentChange();
|
||||||
|
$('#body-tabs a:first').tab('show')
|
||||||
var top = $('#message-container').offset().top - navBarOffset;
|
var top = $('#message-container').offset().top - navBarOffset;
|
||||||
$(window).scrollTop(top);
|
$(window).scrollTop(top);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
class="btn btn-primary"
|
class="btn btn-primary"
|
||||||
onClick="htmlView('{{.message.ID}}');">
|
onClick="htmlView('{{.message.ID}}');">
|
||||||
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
|
<span class="glyphicon glyphicon-new-window" aria-hidden="true"></span>
|
||||||
HTML
|
Raw HTML
|
||||||
</button>
|
</button>
|
||||||
{{end}}
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
@@ -78,7 +78,22 @@
|
|||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
<div class="message-body">{{.body}}</div>
|
<nav>
|
||||||
|
<ul id="body-tabs" class="nav nav-tabs" role="tablist">
|
||||||
|
{{if .htmlAvailable}}
|
||||||
|
<li role="presentation">
|
||||||
|
<a href="#body-html" aria-controls="body-html" role="tab" data-toggle="tab">Safe HTML</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
<li role="presentation">
|
||||||
|
<a href="#body-text" aria-controls="body-text" role="tab" data-toggle="tab">Plain Text</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="tab-content">
|
||||||
|
<div role="tabpanel" class="tab-pane message-body" id="body-html">{{.htmlBody}}</div>
|
||||||
|
<div role="tabpanel" class="tab-pane message-body" id="body-text">{{.body}}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{{with .attachments}}
|
{{with .attachments}}
|
||||||
<div class="well message-attachments">
|
<div class="well message-attachments">
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/jhillyerd/inbucket/datastore"
|
"github.com/jhillyerd/inbucket/datastore"
|
||||||
"github.com/jhillyerd/inbucket/httpd"
|
"github.com/jhillyerd/inbucket/httpd"
|
||||||
"github.com/jhillyerd/inbucket/log"
|
"github.com/jhillyerd/inbucket/log"
|
||||||
|
"github.com/jhillyerd/inbucket/sanitize"
|
||||||
"github.com/jhillyerd/inbucket/stringutil"
|
"github.com/jhillyerd/inbucket/stringutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,6 +119,14 @@ func MailboxShow(w http.ResponseWriter, req *http.Request, ctx *httpd.Context) (
|
|||||||
}
|
}
|
||||||
body := template.HTML(httpd.TextToHTML(mime.Text))
|
body := template.HTML(httpd.TextToHTML(mime.Text))
|
||||||
htmlAvailable := mime.HTML != ""
|
htmlAvailable := mime.HTML != ""
|
||||||
|
var htmlBody template.HTML
|
||||||
|
if htmlAvailable {
|
||||||
|
if str, err := sanitize.HTML(mime.HTML); err == nil {
|
||||||
|
htmlBody = template.HTML(str)
|
||||||
|
} else {
|
||||||
|
log.Warnf("HTML sanitizer failed: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
// Render partial template
|
// Render partial template
|
||||||
return httpd.RenderPartial("mailbox/_show.html", w, map[string]interface{}{
|
return httpd.RenderPartial("mailbox/_show.html", w, map[string]interface{}{
|
||||||
"ctx": ctx,
|
"ctx": ctx,
|
||||||
@@ -125,6 +134,7 @@ func MailboxShow(w http.ResponseWriter, req *http.Request, ctx *httpd.Context) (
|
|||||||
"message": msg,
|
"message": msg,
|
||||||
"body": body,
|
"body": body,
|
||||||
"htmlAvailable": htmlAvailable,
|
"htmlAvailable": htmlAvailable,
|
||||||
|
"htmlBody": htmlBody,
|
||||||
"mimeErrors": mime.Errors,
|
"mimeErrors": mime.Errors,
|
||||||
"attachments": mime.Attachments,
|
"attachments": mime.Attachments,
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user