Skip to content

Commit

Permalink
Merge branch 'release/0.7.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
jhillyerd committed Nov 24, 2019
2 parents 48138bd + 9c98ce6 commit 39591e3
Show file tree
Hide file tree
Showing 29 changed files with 2,666 additions and 140 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ after_success:
go:
- "1.11.x"
- "1.12.x"
- "1.13.x"
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@ Change Log
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [0.7.0] - 2019-11-24

### Added
- Public DecodeHeaders function for getting header data without processing the
body parts (thanks requaos.)
- Test coverage over 90% (thanks requaos!)

### Changed
- Update dependencies

### Fixed
- Do not attempt to detect character set for short messages (#131, thanks
requaos.)
- Possible slice out of bounds error (#134, thanks requaos.)
- Tests on Go 1.13 no longer fail due to textproto change (#137, thanks to
requaos.)


## [0.6.0] - 2019-08-10

### Added
Expand Down Expand Up @@ -96,6 +114,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- Initial implementation of MIME encoding, using `enmime.MailBuilder`

[Unreleased]: https://github.com/jhillyerd/enmime/compare/master...develop
[0.6.0]: https://github.com/jhillyerd/enmime/compare/v0.6.0...v0.7.0
[0.6.0]: https://github.com/jhillyerd/enmime/compare/v0.5.0...v0.6.0
[0.5.0]: https://github.com/jhillyerd/enmime/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/jhillyerd/enmime/compare/v0.3.0...v0.4.0
Expand Down
37 changes: 37 additions & 0 deletions boundary_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"io/ioutil"
"strings"
"testing"

"github.com/pkg/errors"
)

func TestBoundaryReader(t *testing.T) {
Expand Down Expand Up @@ -420,3 +422,38 @@ func TestBoundaryReaderBufferBoundaryCross(t *testing.T) {
t.Errorf("ReadAll() got: %q, want: %q", got, want)
}
}

func TestBoundaryReaderReadErrors(t *testing.T) {
// Destination byte slice is shorter than buffer length
dest := make([]byte, 1)
br := &boundaryReader{
buffer: bytes.NewBuffer([]byte{'1', '2', '3'}),
}
n, err := br.Read(dest)
if n != 1 {
t.Fatal("Read() did not read bytes equal to len(dest), failed")
}
if err != nil {
t.Fatal("Read() should not have returned an error, failed")
}

// Using bufio.Reader with a 0 length buffer will cause
// Peek method to return a non io.EOF error.
dest = make([]byte, 10)
br.r = &bufio.Reader{}
n, err = br.Read(dest)
if n != 0 {
t.Fatal("Read() should not have read any bytes, failed")
}
if errors.Cause(err) != bufio.ErrBufferFull {
t.Fatal("Read() should have returned bufio.ErrBufferFull error, failed")
}
// Next method to return a non io.EOF error.
next, err := br.Next()
if next {
t.Fatal("Next() should have returned false, failed")
}
if errors.Cause(err) != bufio.ErrBufferFull {
t.Fatal("Read() should have returned bufio.ErrBufferFull error, failed")
}
}
28 changes: 28 additions & 0 deletions builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ package enmime_test
import (
"bytes"
"net/mail"
"net/smtp"
"path/filepath"
"reflect"
"strconv"
"strings"
"testing"
"time"

Expand Down Expand Up @@ -923,3 +925,29 @@ func TestBuilderQPHeaders(t *testing.T) {

test.DiffGolden(t, b.Bytes(), "testdata", "encode", "build-qp-addr-headers.golden")
}

func TestSend(t *testing.T) {
// Satisfy all block of the Send method and use
// an intentionally malformed From Address to
// elicit an expected error from smtp.SendMail,
// which can be type-checked and verified.
text := "test text body"
html := "test html body"
a := enmime.Builder().
Text([]byte(text)).
HTML([]byte(html)).
From("name", "foo\rbar").
Subject("foo").
ToAddrs(addrSlice).
CCAddrs(addrSlice).
BCCAddrs(addrSlice)
// Dummy SMTP Authentication
auth := smtp.PlainAuth("", "[email protected]", "password", "mail.example.com")
err := a.Send("0.0.0.0", auth)
if err == nil {
t.Fatal("Send() did not return expected error, failed")
}
if !strings.Contains(err.Error(), "smtp: A line must not contain CR or LF") {
t.Fatalf("Send() did not return expected error, failed: %s", err.Error())
}
}
45 changes: 33 additions & 12 deletions cmd/mime-dump/mime-dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,56 @@ package main

import (
"fmt"
"io"
"os"
"path/filepath"

"github.com/jhillyerd/enmime"
"github.com/jhillyerd/enmime/cmd"
)

type dumper struct {
errOut, stdOut io.Writer
exit exitFunc
}
type exitFunc func(int)

func newDefaultDumper() *dumper {
return &dumper{
errOut: os.Stderr,
stdOut: os.Stdout,
exit: os.Exit,
}
}

func main() {
if len(os.Args) < 2 {
fmt.Fprintln(os.Stderr, "Missing filename argument")
os.Exit(1)
d := newDefaultDumper()
d.exit(d.dump(os.Args))
}

func (d *dumper) dump(args []string) int {
if len(args) < 2 {
fmt.Fprintln(d.errOut, "Missing filename argument")
return 1
}

reader, err := os.Open(os.Args[1])
reader, err := os.Open(args[1])
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to open file:", err)
os.Exit(1)
fmt.Fprintln(d.errOut, "Failed to open file:", err)
return 1
}

// basename is used as the markdown title
basename := filepath.Base(os.Args[1])
basename := filepath.Base(args[1])
e, err := enmime.ReadEnvelope(reader)
if err != nil {
fmt.Fprintln(os.Stderr, "During enmime.ReadEnvelope:", err)
os.Exit(1)
fmt.Fprintln(d.errOut, "During enmime.ReadEnvelope:", err)
return 1
}

if err = cmd.EnvelopeToMarkdown(os.Stdout, e, basename); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
if err = cmd.EnvelopeToMarkdown(d.stdOut, e, basename); err != nil {
fmt.Fprintln(d.errOut, err)
return 1
}
return 0
}
75 changes: 75 additions & 0 deletions cmd/mime-dump/mime-dump_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"bytes"
"path/filepath"
"strings"
"testing"
)

func TestNewDefaultDumper(t *testing.T) {
if newDefaultDumper() == nil {
t.Fatal("Dumper instance should not be nil")
}
}

func TestNotEnoughArgs(t *testing.T) {
b := &bytes.Buffer{}
d := &dumper{
errOut: b,
}
exitCode := d.dump(nil)
if exitCode != 1 {
t.Fatal("Should have returned an exit code of 1, failed")
}
if b.String() != "Missing filename argument\n" {
t.Fatal("Should be missing filename argument, failed")
}
}

func TestFailedToOpenFile(t *testing.T) {
b := &bytes.Buffer{}
d := &dumper{
errOut: b,
}
exitCode := d.dump([]string{"", ""})
if exitCode != 1 {
t.Fatal("Should have returned an exit code of 1, failed")
}
if !strings.HasPrefix(b.String(), "Failed to open file") {
t.Fatal("Should have failed to open file, failed")
}
}

func TestFailedToParseFile(t *testing.T) {
b := &bytes.Buffer{}
d := &dumper{
errOut: b,
}
exitCode := d.dump([]string{"", filepath.Join("..", "..", "testdata", "mail", "erroneous.raw")})
if exitCode != 1 {
t.Fatal("Should have returned an exit code of 1, failed")
}
if !strings.HasPrefix(b.String(), "During enmime.ReadEnvelope") {
t.Fatal("Should have failed to parse file, failed")
}
}

func TestSuccess(t *testing.T) {
b := &bytes.Buffer{}
s := &bytes.Buffer{}
d := &dumper{
errOut: b,
stdOut: s,
}
exitCode := d.dump([]string{"", filepath.Join("..", "..", "testdata", "mail", "attachment.raw")})
if exitCode != 0 {
t.Fatal("Should have returned an exit code of 0, failed")
}
if b.Len() > 0 {
t.Fatal("Should not have produced any errors, failed")
}
if !(s.Len() > 0) {
t.Fatal("Should have printed markdown document, failed")
}
}
75 changes: 51 additions & 24 deletions cmd/mime-extractor/mime-extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package main
import (
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -17,51 +18,77 @@ var (
outdir = flag.String("o", "", "output dir")
)

type extractor struct {
errOut, stdOut io.Writer
exit exitFunc
wd workingDir
fileWrite attachmentWriter
}

type exitFunc func(int)
type workingDir func() (string, error)
type attachmentWriter func(string, []byte, os.FileMode) error

func newDefaultExtractor() *extractor {
return &extractor{
errOut: os.Stderr,
stdOut: os.Stdout,
exit: os.Exit,
wd: os.Getwd,
fileWrite: ioutil.WriteFile,
}
}

func main() {
flag.Parse()
if *mimefile == "" {
fmt.Fprintln(os.Stderr, "Missing filename argument")
ex := newDefaultExtractor()
ex.exit(ex.extract(*mimefile, *outdir))
}

func (ex *extractor) extract(file, dir string) int {
if file == "" {
fmt.Fprintln(ex.errOut, "Missing filename argument")
flag.Usage()
os.Exit(1)
return 1
}
cwd, _ := os.Getwd()
if *outdir == "" {
outdir = &cwd
if dir == "" {
dir, _ = ex.wd()
}

if err := os.MkdirAll(*outdir, os.ModePerm); err != nil {
fmt.Fprintf(os.Stderr, "Mkdir %s failed.", *outdir)
os.Exit(2)
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
fmt.Fprintf(ex.errOut, "Mkdir %s failed.", dir)
return 2
}

reader, err := os.Open(*mimefile)
reader, err := os.Open(file)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to open file:", err)
os.Exit(1)
fmt.Fprintln(ex.errOut, "Failed to open file:", err)
return 1
}

// basename is used as the markdown title
basename := filepath.Base(*mimefile)
basename := filepath.Base(file)
e, err := enmime.ReadEnvelope(reader)
if err != nil {
fmt.Fprintln(os.Stderr, "During enmime.ReadEnvelope:", err)
os.Exit(1)
fmt.Fprintln(ex.errOut, "During enmime.ReadEnvelope:", err)
return 1
}

if err = cmd.EnvelopeToMarkdown(os.Stdout, e, basename); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
if err = cmd.EnvelopeToMarkdown(ex.stdOut, e, basename); err != nil {
fmt.Fprintln(ex.errOut, err)
return 1
}

// Write out attachments
fmt.Fprintf(os.Stderr, "\nExtracting attachments into %s...", *outdir)
// Write errOut attachments
fmt.Fprintf(ex.errOut, "\nExtracting attachments into %s...", dir)
for _, a := range e.Attachments {
newFileName := filepath.Join(*outdir, a.FileName)
err = ioutil.WriteFile(newFileName, a.Content, 0644)
newFileName := filepath.Join(dir, a.FileName)
err = ex.fileWrite(newFileName, a.Content, 0644)
if err != nil {
fmt.Printf("Error writing file %q: %v\n", newFileName, err)
fmt.Fprintf(ex.stdOut, "Error writing file %q: %v\n", newFileName, err)
break
}
}
fmt.Fprintln(os.Stderr, " Done!")
fmt.Fprintln(ex.errOut, " Done!")
return 0
}
Loading

0 comments on commit 39591e3

Please sign in to comment.