Repository: jhillyerd/enmime
Pull request #299 (2023-08-14 21:55:31)

Rollback pkg/errors portion of #298
PR #298 removed usage of the archived package github.com/pkg/errors, and
references to the deprecated ioutil package.

After understanding that stdlib errors still does not support stack trace
wrapping (see discussion in #298), we have decided to rollback these changes
as parser debugging heavily benefits from stack traces.

The ioutil changes are desirable and will be kept.

See also #94 


coveralls: 
[![Coverage Status](https://coveralls.io/builds/61981131/badge)](https://coveralls.io/builds/61981131)

coverage: 85.955% (-0.02%) from 85.97% when pulling **e0550a73054006bc96bc0e081934b920ef7ee36f on revert-298** into **3c35f97f05e559326a213a7cfa93b9ac77cba1a1 on main**.


Patch:
diff --git a/boundary.go b/boundary.go
index 12e1728..be4a43f 100644
--- a/boundary.go
+++ b/boundary.go
@@ -3,10 +3,11 @@ package enmime
 import (
 	"bufio"
 	"bytes"
-	"errors"
-	"fmt"
+	stderrors "errors"
 	"io"
 	"unicode"
+
+	"github.com/pkg/errors"
 )
 
 // This constant needs to be at least 76 for this package to work correctly.  This is because
@@ -14,7 +15,7 @@ import (
 // from it.
 const peekBufferSize = 4096
 
-var errNoBoundaryTerminator = errors.New("expected boundary not present")
+var errNoBoundaryTerminator = stderrors.New("expected boundary not present")
 
 type boundaryReader struct {
 	finished    bool          // No parts remain when finished
@@ -82,7 +83,7 @@ func (b *boundaryReader) Read(dest []byte) (n int, err error) {
 		var cs []byte
 		cs, err = b.r.Peek(1)
 		if err != nil && err != io.EOF {
-			return 0, fmt.Errorf("failed to read content: %w", err)
+			return 0, errors.WithStack(err)
 		}
 		// Ensure that we can switch on the first byte of 'cs' without panic.
 		if len(cs) > 0 {
@@ -131,7 +132,7 @@ func (b *boundaryReader) Read(dest []byte) (n int, err error) {
 							}
 							return n, io.EOF
 						default:
-							return 0, fmt.Errorf("failed to read content: %w", err)
+							return 0, errors.WithStack(err)
 						}
 					}
 				case io.EOF:
@@ -153,11 +154,11 @@ func (b *boundaryReader) Read(dest []byte) (n int, err error) {
 				break
 			}
 
-			return 0, fmt.Errorf("failed to read content: %w", err)
+			return 0, errors.WithStack(err)
 		}
 
 		if err = b.buffer.WriteByte(next); err != nil {
-			return 0, fmt.Errorf("failed to read content: %w", err)
+			return 0, errors.WithStack(err)
 		}
 	}
 
@@ -194,7 +195,7 @@ func (b *boundaryReader) Next() (bool, error) {
 			if err == nil || err == io.EOF {
 				break
 			} else if err != bufio.ErrBufferFull || len(segment) == 0 {
-				return false, fmt.Errorf("failed to move over the boundary: %w", err)
+				return false, errors.WithStack(err)
 			}
 		}
 
@@ -222,7 +223,7 @@ func (b *boundaryReader) Next() (bool, error) {
 			continue
 		}
 		b.finished = true
-		return false, fmt.Errorf("expecting boundary %q, got %q: %w", string(b.prefix), string(line), errNoBoundaryTerminator)
+		return false, errors.WithMessagef(errNoBoundaryTerminator, "expecting boundary %q, got %q", string(b.prefix), string(line))
 	}
 }
 
diff --git a/envelope.go b/envelope.go
index 6ee73fa..804a7c2 100644
--- a/envelope.go
+++ b/envelope.go
@@ -12,6 +12,8 @@ import (
 	"github.com/jhillyerd/enmime/internal/coding"
 	"github.com/jhillyerd/enmime/internal/textproto"
 	"github.com/jhillyerd/enmime/mediatype"
+
+	"github.com/pkg/errors"
 )
 
 // Envelope is a simplified wrapper for MIME email messages.
@@ -156,7 +158,7 @@ func (p Parser) ReadEnvelope(r io.Reader) (*Envelope, error) {
 	// Read MIME parts from reader
 	root, err := p.ReadParts(r)
 	if err != nil {
-		return nil, fmt.Errorf("failed to ReadParts: %w", err)
+		return nil, errors.WithMessage(err, "Failed to ReadParts")
 	}
 	return p.EnvelopeFromPart(root)
 }
diff --git a/go.mod b/go.mod
index 44e8df0..dfaa26f 100644
--- a/go.mod
+++ b/go.mod
@@ -7,6 +7,7 @@ require (
 	github.com/go-test/deep v1.0.7
 	github.com/gogs/chardet v0.0.0-20191104214054-4b6791f73a28
 	github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7
+	github.com/pkg/errors v0.9.1
 	github.com/stretchr/testify v1.7.0
 	golang.org/x/text v0.8.0
 )
diff --git a/go.sum b/go.sum
index 918dda7..8191915 100644
--- a/go.sum
+++ b/go.sum
@@ -13,6 +13,8 @@ github.com/mattn/go-runewidth v0.0.12 h1:Y41i/hVW3Pgwr8gV+J23B9YEY0zxjptBuCWEaxm
 github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
 github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
 github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
diff --git a/header.go b/header.go
index 0d8e3f6..ac86281 100644
--- a/header.go
+++ b/header.go
@@ -12,6 +12,8 @@ import (
 	"github.com/jhillyerd/enmime/internal/stringutil"
 	"github.com/jhillyerd/enmime/internal/textproto"
 	"github.com/jhillyerd/enmime/mediatype"
+
+	"github.com/pkg/errors"
 )
 
 const (
@@ -192,10 +194,7 @@ line:
 	buf.Write([]byte{'\r', '\n'})
 	tr := textproto.NewReader(bufio.NewReader(buf))
 	header, err := tr.ReadEmailMIMEHeader()
-	if err != nil {
-		return nil, fmt.Errorf("failed to read header: %w", err)
-	}
-	return header, nil
+	return header, errors.WithStack(err)
 }
 
 // decodeToUTF8Base64Header decodes a MIME header per RFC 2047, reencoding to =?utf-8b?
diff --git a/inspect.go b/inspect.go
index 94f13e2..268d570 100644
--- a/inspect.go
+++ b/inspect.go
@@ -3,11 +3,12 @@ package enmime
 import (
 	"bufio"
 	"bytes"
-	"errors"
 	"io"
 
 	"github.com/jhillyerd/enmime/internal/coding"
 	"github.com/jhillyerd/enmime/internal/textproto"
+
+	"github.com/pkg/errors"
 )
 
 var defaultHeadersList = []string{
@@ -39,9 +40,10 @@ func DecodeHeaders(b []byte, addtlHeaders ...string) (textproto.MIMEHeader, erro
 	b = ensureHeaderBoundary(b)
 	tr := textproto.NewReader(bufio.NewReader(bytes.NewReader(b)))
 	headers, err := tr.ReadMIMEHeader()
-
-	// io.EOF is expected
-	if err != nil && !errors.Is(err, io.EOF) {
+	switch errors.Cause(err) {
+	case nil, io.EOF:
+	// carry on, io.EOF is expected
+	default:
 		return nil, err
 	}
 	headerList := defaultHeadersList
diff --git a/mediatype/mediatype.go b/mediatype/mediatype.go
index 0ac3957..702d9b7 100644
--- a/mediatype/mediatype.go
+++ b/mediatype/mediatype.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/jhillyerd/enmime/internal/coding"
 	"github.com/jhillyerd/enmime/internal/stringutil"
+	"github.com/pkg/errors"
 )
 
 const (
@@ -43,7 +44,7 @@ func Parse(ctype string) (mtype string, params map[string]string, invalidParams
 		if err.Error() == "mime: no media type" {
 			return "", nil, nil, nil
 		}
-		return "", nil, nil, fmt.Errorf("parsing failed: %w", err)
+		return "", nil, nil, errors.WithStack(err)
 	}
 
 	if mtype == ctPlaceholder {
diff --git a/part.go b/part.go
index 16a71dd..4e05b6f 100644
--- a/part.go
+++ b/part.go
@@ -4,8 +4,6 @@ import (
 	"bufio"
 	"bytes"
 	"encoding/base64"
-	"errors"
-	"fmt"
 	"io"
 	"math/rand"
 	"mime/quotedprintable"
@@ -17,6 +15,8 @@ import (
 	"github.com/jhillyerd/enmime/internal/coding"
 	"github.com/jhillyerd/enmime/internal/textproto"
 	"github.com/jhillyerd/enmime/mediatype"
+
+	"github.com/pkg/errors"
 )
 
 const (
@@ -195,7 +195,7 @@ func (p *Part) convertFromDetectedCharset(r io.Reader, readPartErrorPolicy ReadP
 
 	buf, err := p.readPartContent(r, readPartErrorPolicy)
 	if err != nil {
-		return nil, err
+		return nil, errors.WithStack(err)
 	}
 
 	cs, err := cd.DetectBest(buf)
@@ -304,7 +304,7 @@ func (p *Part) decodeContent(r io.Reader, readPartErrorPolicy ReadPartErrorPolic
 	// Decode and store content.
 	content, err := p.readPartContent(contentReader, readPartErrorPolicy)
 	if err != nil {
-		return p.base64CorruptInputCheck(fmt.Errorf("failed to decode content: %w", err))
+		return p.base64CorruptInputCheck(errors.WithStack(err))
 	}
 	p.Content = content
 	// Collect base64 errors.
@@ -325,11 +325,12 @@ func (p *Part) decodeContent(r io.Reader, readPartErrorPolicy ReadPartErrorPolic
 //
 // It can be used to create ReadPartErrorPolicy functions.
 func IsBase64CorruptInputError(err error) bool {
-	if err == nil {
+	switch errors.Cause(err).(type) {
+	case base64.CorruptInputError:
+		return true
+	default:
 		return false
 	}
-	_, ok := err.(base64.CorruptInputError)
-	return ok
 }
 
 // base64CorruptInputCheck will avoid fatal failure on corrupt base64 input
@@ -406,7 +407,7 @@ func parseParts(parent *Part, reader *bufio.Reader) error {
 	br := newBoundaryReader(reader, parent.Boundary)
 	for indexPartID := 1; true; indexPartID++ {
 		next, err := br.Next()
-		if err != nil && !errors.Is(err, io.EOF) {
+		if err != nil && errors.Cause(err) != io.EOF {
 			return err
 		}
 		if br.unbounded {
@@ -464,7 +465,7 @@ func parseParts(parent *Part, reader *bufio.Reader) error {
 	// Store any content following the closing boundary marker into the epilogue.
 	epilogue, err := io.ReadAll(reader)
 	if err != nil {
-		return fmt.Errorf("failed to parse parts: %w", err)
+		return errors.WithStack(err)
 	}
 	parent.Epilogue = epilogue
 


Test patch:
diff --git a/boundary_test.go b/boundary_test.go
index b786a92..228effc 100644
--- a/boundary_test.go
+++ b/boundary_test.go
@@ -3,10 +3,11 @@ package enmime
 import (
 	"bufio"
 	"bytes"
-	"errors"
 	"io"
 	"strings"
 	"testing"
+
+	"github.com/pkg/errors"
 )
 
 func TestBoundaryReader(t *testing.T) {
@@ -462,7 +463,7 @@ func TestBoundaryReaderReadErrors(t *testing.T) {
 	if n != 0 {
 		t.Fatal("Read() should not have read any bytes, failed")
 	}
-	if !errors.Is(err, bufio.ErrBufferFull) {
+	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.
@@ -470,7 +471,7 @@ func TestBoundaryReaderReadErrors(t *testing.T) {
 	if next {
 		t.Fatal("Next() should have returned false, failed")
 	}
-	if !errors.Is(err, bufio.ErrBufferFull) {
+	if errors.Cause(err) != bufio.ErrBufferFull {
 		t.Fatal("Read() should have returned bufio.ErrBufferFull error, failed")
 	}
 }