Repository: jhillyerd/enmime
Pull request #119 (2019-01-24 18:47:52)

Make decoding failure on base64.CorruptInputError non-fatal
#117 


Patch:
diff --git a/header.go b/header.go
index 3d9dfab..a0075e2 100644
--- a/header.go
+++ b/header.go
@@ -510,6 +510,13 @@ func rfc2047AttributeName(s string) string {
 		return s
 	}
 	pair := strings.SplitAfter(s, "?=")
-	pair[0] = decodeHeader(pair[0])
+	// lets assume that the attribute was encoded because of unicode characters being present
+	// then the attribute value should be quoted
+	keyValuePair := strings.SplitAfter(decodeHeader(pair[0]), "=")
+	// only quote the parameter value if it isn't already quoted
+	if !strings.HasPrefix(keyValuePair[1], "\"") {
+		keyValuePair[1] = fmt.Sprintf("\"%s", keyValuePair[1])
+	}
+	pair[0] = strings.Join(keyValuePair, "")
 	return strings.Join(pair, "")
 }
diff --git a/part.go b/part.go
index e5b7fd9..0e17d0e 100644
--- a/part.go
+++ b/part.go
@@ -284,13 +284,13 @@ func (p *Part) decodeContent(r io.Reader) error {
 		var err error
 		contentReader, err = p.convertFromDetectedCharset(contentReader)
 		if err != nil {
-			return err
+			return p.base64CorruptInputCheck(err)
 		}
 	}
 	// Decode and store content.
 	content, err := ioutil.ReadAll(contentReader)
 	if err != nil {
-		return errors.WithStack(err)
+		return p.base64CorruptInputCheck(errors.WithStack(err))
 	}
 	p.Content = content
 	// Collect base64 errors.
@@ -306,6 +306,24 @@ func (p *Part) decodeContent(r io.Reader) error {
 	return nil
 }
 
+// base64CorruptInputCheck will avoid fatal failure on corrupt base64 input
+//
+// This is a switch on errors.Cause(err).(type) for base64.CorruptInputError
+func (p *Part) base64CorruptInputCheck(err error) error {
+	switch errors.Cause(err).(type) {
+	case base64.CorruptInputError:
+		p.Content = nil
+		p.Errors = append(p.Errors, &Error{
+			Name:   ErrorMalformedBase64,
+			Detail: err.Error(),
+			Severe: true,
+		})
+		return nil
+	default:
+		return err
+	}
+}
+
 // Clone returns a clone of the current Part.
 func (p *Part) Clone(parent *Part) *Part {
 	if p == nil {


Test patch:
diff --git a/envelope_test.go b/envelope_test.go
index 4df32ed..b0aa841 100644
--- a/envelope_test.go
+++ b/envelope_test.go
@@ -900,6 +900,25 @@ func TestBadContentTypeInMime(t *testing.T) {
 	}
 }
 
+func TestBadContentTransferEncodingInMime(t *testing.T) {
+	msg := test.OpenTestData("mail", "mime-bad-content-transfer-encoding.raw")
+	e, err := enmime.ReadEnvelope(msg)
+
+	if err != nil {
+		t.Fatal("Failed to parse MIME:", err)
+	}
+
+	var expectedErrorPresent bool
+	for _, v := range e.Errors {
+		if v.Name == enmime.ErrorMalformedBase64 && v.Severe {
+			expectedErrorPresent = true
+		}
+	}
+	if !expectedErrorPresent {
+		t.Fatal("Mail should have a severe malformed base64 error")
+	}
+}
+
 func TestBlankMediaName(t *testing.T) {
 	msg := test.OpenTestData("mail", "mime-blank-media-name.raw")
 	e, err := enmime.ReadEnvelope(msg)
diff --git a/header_test.go b/header_test.go
index 95b2ec0..0e8d484 100644
--- a/header_test.go
+++ b/header_test.go
@@ -194,6 +194,11 @@ func TestFixMangledMediaType(t *testing.T) {
 			sep:   ";",
 			want:  "one/two; name=\"file.two\"",
 		},
+		{
+			input: "application/octet-stream; =?UTF-8?B?bmFtZT3DsMKfwpTCii5tc2c=?=",
+			sep:   " ",
+			want:  "application/octet-stream;name=\"ð.msg",
+		},
 		{
 			input: "one/two name=\"file.two\" name=\"file.two\"",
 			sep:   " ",
diff --git a/testdata/mail/mime-bad-content-transfer-encoding.raw b/testdata/mail/mime-bad-content-transfer-encoding.raw
new file mode 100644
index 0000000..8754ab4
--- /dev/null
+++ b/testdata/mail/mime-bad-content-transfer-encoding.raw
@@ -0,0 +1,66 @@
+Received: from HVD-Exch16-01. internal.contoso.dom (192.168.1.50) by
+ HVD-Exch16-01. internal.contoso.dom (192.168.1.50) with Microsoft SMTP
+ Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256_P256) id
+ 15.1.1591.10 via Mailbox Transport; Sun, 20 Jan 2019 14:33:43 -0600
+Message-ID: <rgrbhlkyhowz-zughjdb-v@demo.com>
+Content-Type: multipart/alternative; boundary="ABOUNDARY.075224149159994985"
+Subject: alert
+Date: Sun, 20 Jan 2019 23:33:39 +0300
+From: example <example@demo.com>
+To: example <example@demo.com>
+Return-Path: doonglol@contoso.com
+MIME-Version: 1.0
+
+--ABOUNDARY.075224149159994985
+Content-Type: text/plain; format=flowed; charset="UTF-8"
+Content-Transfer-Encoding: base64
+
+ploozdect  nispplul swunkglap zarplontruskdremp fruftsist
+ploystoov  smohsless cregzass smoosknuspgeftlut priptyasp
+smoorbrooft  pukstrooz pruhrept frorblilcroospbrod spakkess
+ruzyoost  toctswing grumsmach birflugjechfapt broostspust
+nudyet  changlig droontmunt chapplooypectsoy gafral
+slipblept  geskchant gluddrach prontcrundhaysnind spomdast
+sahshooy  yinzess snutgruch breyloossstrolflooy vuptsling
+ploorlish  hondcrud mebnush flendblutslawspint groowpromp
+maspflid  grashsmey ploozblust blanzafbumyuct gligplask
+fasstross  voongluf gluzzast luzflooctlerpach brishhonk
+troowtrict  flezskooft honkglok bloofjooshshuhtuz pogpreh
+clinttrost  strachlunk strehslonk flinflumcrashshipt prehhach
+stooplet  tucttoog prindskech bloopdrarhoomgont grabtesp
+prilput  froontnooch nubstrisk lectkampswulgrupt pangyef
+spawsnef  snoondshin noonskunk sperglikvimpcrag flizfan
+frogplusk  claspcrav flacttink blandpahcroskbropt plobnof
+sniwmact  flobskenk grotslish crosppampsooskslut dekprich
+slitpruw  smupwiw skikstint hostslaglinboh glovyev
+groolshict  mirbrod tooddih smendgloshtooctsmak nanclih
+plantfloog  plosszav chosksmek shechsnupfrashdik smooyhipt
+skulclod  glubsmak swakproog snostslahshoplan sledtroosk
+wizpek  voofwoss ploospmoow daskspongbrawbroop suhspat
+jatnoft  wofthah froolyup slootgankcreshglol pidshek
+nimpchest  smichmund clofflask lundzoonkroonsmiss pleskting
+wechslapt  yulfut charshisk jospharpoffroor flubfraw
+smirslang  konsmiy zivblad sterchoowpooyjik huctkul
+blonghum  vaptslooft gloostplemp puttagslesssuft shivvuh
+strongslog  craftjoob lidfreh rizskachbumloow kifkid
+druksnoopt  drumpclir govbluf siptgeptgrozstrooss glarhaft
+flanyef  zetkel kifspunk gefhewsnohpur spewskoosh
+rafgob  zoochbiw foptgav fuyyonkbrictspob plenrimp
+fruchbremp  brooshchep smafbluh shoochcrakkangstrub moontdoomp
+glooctclog  jiffrint droozkuz zopprumpspaftheb joofstub
+blidguss  deshdrect gromstasp clebtusppruskgov dudtrosk
+prudskil  wengdesp stransloh droobglizhondsmop dewspook
+vedsosp  trempwusp siyfrool kuchstrissnookflest croptgum
+leshkess  ploobfrey blintsung rengjictbrushgrooss jiptwiv
+kugstug  bruchkop giftchir piwclisksloozhist frispgech
+glactspang  cruvvob prushswach plegpitwoospvow chuzgrunt
+
+--ABOUNDARY.075224149159994985
+Content-Type: text/html; charset="utf-8"
+Content-Transfer-Encoding: base64
+
+PG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJz
+ZXQ9dXRmLTgiPgoKPGJyPgpUaGlzIGlzIGFuIGV4YW1wbGUKPGJyPg==
+
+
+--ABOUNDARY.075224149159994985--