Skip to content

Commit

Permalink
Adds content-type message header to support other content-types such …
Browse files Browse the repository at this point in the history
…HTML

Closes #112
Closes #250
  • Loading branch information
nikosk686 committed Oct 21, 2019
1 parent d25a45c commit 47dbf56
Show file tree
Hide file tree
Showing 12 changed files with 317 additions and 69 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*eml text eol=crlf
14 changes: 10 additions & 4 deletions cmd/mailchain/internal/http/handlers/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,10 +87,11 @@ func GetMessages(inbox stores.State, receivers map[string]mailbox.Receiver, ks k
messages = append(messages, getMessage{
Body: string(message.Body),
Headers: &getHeaders{
To: message.Headers.To.String(),
From: message.Headers.From.String(),
Date: message.Headers.Date,
MessageID: message.ID.HexString(),
To: message.Headers.To.String(),
From: message.Headers.From.String(),
Date: message.Headers.Date,
MessageID: message.ID.HexString(),
ContentType: message.Headers.ContentType,
},
Read: readStatus,
Subject: message.Headers.Subject,
Expand Down Expand Up @@ -223,4 +224,9 @@ type getHeaders struct {
// example: 47eca011e32b52c71005ad8a8f75e1b44c92c99fd12e43bccfe571e3c2d13d2e9a826a550f5ff63b247af471@mailchain
// readOnly: true
MessageID string `json:"message-id"`
// The content type and the encoding of the message body
// readOnly: true
// example: text/plain; charset=\"UTF-8\",
// text/html; charset=\"UTF-8\"
ContentType string `json:"content-type"`
}
26 changes: 15 additions & 11 deletions internal/mail/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,26 @@ import (
"time"
)

const DefaultContentType = "text/plain; charset=\"UTF-8\""

// NewHeaders create the headers for sending a new message
func NewHeaders(date time.Time, from, to Address, replyTo *Address, subject string) *Headers {
func NewHeaders(date time.Time, from, to Address, replyTo *Address, subject string, contentType string) *Headers {
return &Headers{
From: from,
To: to,
ReplyTo: replyTo,
Subject: subject,
Date: date,
From: from,
To: to,
ReplyTo: replyTo,
Subject: subject,
Date: date,
ContentType: contentType,
}
}

// Headers for the message
type Headers struct {
From Address
To Address
Date time.Time
Subject string
ReplyTo *Address
From Address
To Address
Date time.Time
Subject string
ReplyTo *Address
ContentType string
}
53 changes: 38 additions & 15 deletions internal/mail/headers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@ import (
func TestNewHeaders(t *testing.T) {
assert := assert.New(t)
type args struct {
date time.Time
from Address
to Address
replyTo *Address
subject string
date time.Time
from Address
to Address
replyTo *Address
subject string
contentType string
}
tests := []struct {
name string
Expand All @@ -43,12 +44,32 @@ func TestNewHeaders(t *testing.T) {
Address{ChainAddress: "0x92d8f10248c6a3953cc3692a894655ad05d61efb", DisplayName: "", FullAddress: "[email protected]"},
nil,
"Hello World",
"text/plain; charset=\"UTF-8\"",
},
&Headers{
Date: time.Date(2001, 01, 02, 03, 04, 5, 6, time.UTC),
From: Address{ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
To: Address{ChainAddress: "0x92d8f10248c6a3953cc3692a894655ad05d61efb", DisplayName: "", FullAddress: "[email protected]"},
Subject: "Hello World",
Date: time.Date(2001, 01, 02, 03, 04, 5, 6, time.UTC),
From: Address{ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
To: Address{ChainAddress: "0x92d8f10248c6a3953cc3692a894655ad05d61efb", DisplayName: "", FullAddress: "[email protected]"},
Subject: "Hello World",
ContentType: "text/plain; charset=\"UTF-8\"",
},
},
{
"html",
args{
time.Date(2001, 01, 02, 03, 04, 5, 6, time.UTC),
Address{ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
Address{ChainAddress: "0x92d8f10248c6a3953cc3692a894655ad05d61efb", DisplayName: "", FullAddress: "[email protected]"},
nil,
"Hello World",
"text/html; charset=\"UTF-8\"",
},
&Headers{
Date: time.Date(2001, 01, 02, 03, 04, 5, 6, time.UTC),
From: Address{ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
To: Address{ChainAddress: "0x92d8f10248c6a3953cc3692a894655ad05d61efb", DisplayName: "", FullAddress: "[email protected]"},
Subject: "Hello World",
ContentType: "text/html; charset=\"UTF-8\"",
},
},
{
Expand All @@ -59,19 +80,21 @@ func TestNewHeaders(t *testing.T) {
Address{ChainAddress: "0x92d8f10248c6a3953cc3692a894655ad05d61efb", DisplayName: "", FullAddress: "[email protected]"},
&Address{ChainAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2"},
"Hello World",
"text/plain; charset=\"UTF-8\"",
},
&Headers{
Date: time.Date(2001, 01, 02, 03, 04, 5, 6, time.UTC),
From: Address{ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
To: Address{ChainAddress: "0x92d8f10248c6a3953cc3692a894655ad05d61efb", DisplayName: "", FullAddress: "[email protected]"},
ReplyTo: &Address{ChainAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2"},
Subject: "Hello World",
Date: time.Date(2001, 01, 02, 03, 04, 5, 6, time.UTC),
From: Address{ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
To: Address{ChainAddress: "0x92d8f10248c6a3953cc3692a894655ad05d61efb", DisplayName: "", FullAddress: "[email protected]"},
ReplyTo: &Address{ChainAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2"},
Subject: "Hello World",
ContentType: "text/plain; charset=\"UTF-8\"",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewHeaders(tt.args.date, tt.args.from, tt.args.to, tt.args.replyTo, tt.args.subject)
got := NewHeaders(tt.args.date, tt.args.from, tt.args.to, tt.args.replyTo, tt.args.subject, tt.args.contentType)
if !assert.Equal(got, tt.want) {
t.Errorf("NewHeaders() = %v, want %v", got, tt.want)
}
Expand Down
20 changes: 19 additions & 1 deletion internal/mail/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
package mail

import (
"fmt"
"net/http"
"strings"
"time"

"github.com/pkg/errors"
Expand All @@ -26,11 +29,26 @@ func NewMessage(date time.Time, from, to Address, replyTo *Address, subject stri

return &Message{
ID: id,
Headers: NewHeaders(date, from, to, replyTo, subject),
Headers: NewHeaders(date, from, to, replyTo, subject, detectContentType(body)),
Body: body,
}, errors.WithMessage(err, "could not create ID")
}

func detectContentType(body []byte) string {
contentType := http.DetectContentType(body)
result := strings.Split(contentType, ";")

if len(result) == 1 {
return result[0]
} else if len(result) == 2 {
encodingParts := strings.Split(result[1], "=")
encoding := fmt.Sprintf("%s=\"%s\"", encodingParts[0], strings.ToUpper(encodingParts[1]))
return fmt.Sprintf("%s;%s", result[0], encoding)
}

return DefaultContentType
}

// Message Mailchain message.
type Message struct {
Headers *Headers
Expand Down
35 changes: 35 additions & 0 deletions internal/mail/message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,38 @@ func TestNewMessage(t *testing.T) {
})
}
}

func Test_detectContentType(t *testing.T) {
type args struct {
body []byte
}
tests := []struct {
name string
args args
want string
}{
{
"content-type-and-encoding",
args{
[]byte("this is plain text message"),
},
"text/plain; charset=\"UTF-8\"",
},
{
"content-type-and-encoding-html",
args{
[]byte("<h1>this is HTML text message</h1>"),
},
"text/html; charset=\"UTF-8\"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := detectContentType(tt.args.body)
if got != tt.want {
t.Errorf("detectContentType() got = %v, want %v", got, tt.want)
return
}
})
}
}
18 changes: 14 additions & 4 deletions internal/mail/rfc2822/headers.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,11 @@ func parseHeaders(h nm.Header) (*mail.Headers, error) {
return nil, errors.WithMessage(err, "failed to parse `from`")
}
return &mail.Headers{
Date: *date,
Subject: subject,
To: *to,
From: *from,
Date: *date,
Subject: subject,
To: *to,
From: *from,
ContentType: parseContentType(h),
}, nil
}

Expand Down Expand Up @@ -96,3 +97,12 @@ func parseSubject(h nm.Header) (string, error) {

return sources[0], nil
}

func parseContentType(h nm.Header) string {
sources, ok := h["Content-Type"]
if !ok || len(sources) == 0 || len(sources[0]) == 0 {
return mail.DefaultContentType
}

return sources[0]
}
108 changes: 102 additions & 6 deletions internal/mail/rfc2822/headers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,61 @@ func Test_parseTo(t *testing.T) {
}
}

func Test_parseContentType(t *testing.T) {
assert := assert.New(t)
type args struct {
h nm.Header
}
tests := []struct {
name string
args args
want string
}{
{
"success-plain-text",
args{
nm.Header{
"Content-Type": []string{"text/plain; charset=\"UTF-8\""},
},
},
"text/plain; charset=\"UTF-8\"",
},
{
"success-empty-header-to-default",
args{
nm.Header{},
},
"text/plain; charset=\"UTF-8\"",
},
{
"success-empty-header-value-to-default",
args{
nm.Header{
"Content-Type": []string{""},
},
},
"text/plain; charset=\"UTF-8\"",
},
{
"success-html-text",
args{
nm.Header{
"Content-Type": []string{"text/html; charset=\"UTF-8\""},
},
},
"text/html; charset=\"UTF-8\"",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseContentType(tt.args.h)
if !assert.Equal(tt.want, got) {
t.Errorf("parseTo() = %v, want %v", got, tt.want)
}
})
}
}

func Test_parseHeaders(t *testing.T) {
assert := assert.New(t)
type args struct {
Expand All @@ -263,7 +318,47 @@ func Test_parseHeaders(t *testing.T) {
wantErr bool
}{
{
"success",
"success-plain-text",
args{
nm.Header{
"Subject": []string{"test subject"},
"To": []string{"<5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761@ropsten.ethereum>"},
"From": []string{"<4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2@ropsten.ethereum>"},
"Date": []string{"Tue, 12 Mar 2019 20:23:13 UTC"},
"Content-Type": []string{"text/plain; charset=\"UTF-8\""},
},
},
&mail.Headers{
From: mail.Address{DisplayName: "", FullAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2@ropsten.ethereum", ChainAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2"},
To: mail.Address{DisplayName: "", FullAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761@ropsten.ethereum", ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
Date: time.Date(2019, 03, 12, 20, 23, 13, 0, time.UTC),
Subject: "test subject",
ReplyTo: nil,
ContentType: "text/plain; charset=\"UTF-8\""},
false,
},
{
"success-plain-html",
args{
nm.Header{
"Subject": []string{"test subject"},
"To": []string{"<5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761@ropsten.ethereum>"},
"From": []string{"<4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2@ropsten.ethereum>"},
"Date": []string{"Tue, 12 Mar 2019 20:23:13 UTC"},
"Content-Type": []string{"text/html; charset=\"UTF-8\""},
},
},
&mail.Headers{
From: mail.Address{DisplayName: "", FullAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2@ropsten.ethereum", ChainAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2"},
To: mail.Address{DisplayName: "", FullAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761@ropsten.ethereum", ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
Date: time.Date(2019, 03, 12, 20, 23, 13, 0, time.UTC),
Subject: "test subject",
ReplyTo: nil,
ContentType: "text/html; charset=\"UTF-8\""},
false,
},
{
"success-defaultContentType",
args{
nm.Header{
"Subject": []string{"test subject"},
Expand All @@ -273,11 +368,12 @@ func Test_parseHeaders(t *testing.T) {
},
},
&mail.Headers{
From: mail.Address{DisplayName: "", FullAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2@ropsten.ethereum", ChainAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2"},
To: mail.Address{DisplayName: "", FullAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761@ropsten.ethereum", ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
Date: time.Date(2019, 03, 12, 20, 23, 13, 0, time.UTC),
Subject: "test subject",
ReplyTo: nil},
From: mail.Address{DisplayName: "", FullAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2@ropsten.ethereum", ChainAddress: "4cb0a77b76667dac586c40cc9523ace73b5d772bd503c63ed0ca596eae1658b2"},
To: mail.Address{DisplayName: "", FullAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761@ropsten.ethereum", ChainAddress: "5602ea95540bee46d03ba335eed6f49d117eab95c8ab8b71bae2cdd1e564a761"},
Date: time.Date(2019, 03, 12, 20, 23, 13, 0, time.UTC),
Subject: "test subject",
ReplyTo: nil,
ContentType: "text/plain; charset=\"UTF-8\""},
false,
},
{
Expand Down
Loading

0 comments on commit 47dbf56

Please sign in to comment.