Repository: jhillyerd/enmime
Pull request #96 (2018-11-26 11:19:18)

Add file modification date to Content-Disposition headers

jhillyerd: Thanks for sending this PR.  I only accept PRs against the development branch, see https://github.com/jhillyerd/enmime/blob/master/CONTRIBUTING.md.  Please rebase and retarget this PR.

Also, at minimum I'd want to see a test confirming the operation of this change, likely added to both of these:

https://github.com/jhillyerd/enmime/blob/4b02260e35dcdb464660222fe78272114888210a/encode_test.go#L33

https://github.com/jhillyerd/enmime/blob/4b02260e35dcdb464660222fe78272114888210a/encode_test.go#L50

Ideally we would also populate this new field when decoding, as it's confusing to expose a field we leave empty.

dann7387: @jhillyerd The changes that you have requested have been completed.
jhillyerd: Look great, thank you!

Patch:
diff --git a/encode.go b/encode.go
index dd495f2..7934eb1 100644
--- a/encode.go
+++ b/encode.go
@@ -8,6 +8,7 @@ import (
 	"mime/quotedprintable"
 	"net/textproto"
 	"sort"
+	"time"
 
 	"github.com/jhillyerd/enmime/internal/coding"
 	"github.com/jhillyerd/enmime/internal/stringutil"
@@ -109,6 +110,9 @@ func (p *Part) setupMIMEHeaders() transferEncoding {
 		// Build disposition header.
 		param := make(map[string]string)
 		setParamValue(param, hpFilename, stringutil.ToASCII(p.FileName))
+		if !p.FileModDate.IsZero() {
+			setParamValue(param, hpModDate, p.FileModDate.Format(time.RFC822))
+		}
 		mt := mime.FormatMediaType(p.Disposition, param)
 		if mt == "" {
 			// There was an error, FormatMediaType couldn't encode the params.
diff --git a/header.go b/header.go
index 88872ef..30338b8 100644
--- a/header.go
+++ b/header.go
@@ -54,6 +54,7 @@ const (
 	hpFile     = "file"
 	hpFilename = "filename"
 	hpName     = "name"
+	hpModDate  = "modification-date"
 
 	utf8 = "utf-8"
 )
diff --git a/part.go b/part.go
index 907d524..5fc06b5 100644
--- a/part.go
+++ b/part.go
@@ -11,6 +11,7 @@ import (
 	"net/textproto"
 	"strconv"
 	"strings"
+	"time"
 
 	"github.com/gogs/chardet"
 	"github.com/jhillyerd/enmime/internal/coding"
@@ -28,13 +29,14 @@ type Part struct {
 	NextSibling *Part                // NextSibling of this part.
 	Header      textproto.MIMEHeader // Header for this Part.
 
-	Boundary    string // Boundary marker used within this part.
-	ContentID   string // ContentID header for cid URL scheme.
-	ContentType string // ContentType header without parameters.
-	Disposition string // Content-Disposition header without parameters.
-	FileName    string // The file-name from disposition or type header.
-	Charset     string // The content charset encoding, may differ from charset in header.
-	OrigCharset string // The original content charset when a different charset was detected.
+	Boundary    string    // Boundary marker used within this part.
+	ContentID   string    // ContentID header for cid URL scheme.
+	ContentType string    // ContentType header without parameters.
+	Disposition string    // Content-Disposition header without parameters.
+	FileName    string    // The file-name from disposition or type header.
+	FileModDate time.Time // The modification date of the file.
+	Charset     string    // The content charset encoding, may differ from charset in header.
+	OrigCharset string    // The original content charset when a different charset was detected.
 
 	Errors   []*Error // Errors encountered while parsing this part.
 	Content  []byte   // Content after decoding, UTF-8 conversion if applicable.
@@ -153,6 +155,9 @@ func (p *Part) setupContentHeaders(mediaParams map[string]string) {
 	if p.Charset == "" {
 		p.Charset = mediaParams[hpCharset]
 	}
+	if p.FileModDate.IsZero() {
+		p.FileModDate, _ = time.Parse(time.RFC822, mediaParams[hpModDate])
+	}
 }
 
 // decodeContent performs transport decoding (base64, quoted-printable) and charset decoding,


Test patch:
diff --git a/encode_test.go b/encode_test.go
index ca3ec9f..ff48df0 100644
--- a/encode_test.go
+++ b/encode_test.go
@@ -3,6 +3,7 @@ package enmime_test
 import (
 	"bytes"
 	"testing"
+	"time"
 
 	"github.com/jhillyerd/enmime"
 	"github.com/jhillyerd/enmime/internal/test"
@@ -37,6 +38,7 @@ func TestEncodePartDefaultHeaders(t *testing.T) {
 	p.ContentID = "mycontentid"
 	p.Disposition = "attachment"
 	p.FileName = "stuff.zip"
+	p.FileModDate, _ = time.Parse(time.RFC822, "01 Feb 03 04:05 GMT")
 	p.Content = []byte("ZIPZIPZIP")
 
 	b := &bytes.Buffer{}
@@ -54,6 +56,7 @@ func TestEncodePartQuotedHeaders(t *testing.T) {
 	p.ContentID = "mycontentid"
 	p.Disposition = "attachment"
 	p.FileName = `árvíztűrő "x" tükörfúrógép.zip`
+	p.FileModDate, _ = time.Parse(time.RFC822, "01 Feb 03 04:05 GMT")
 	p.Content = []byte("ZIPZIPZIP")
 
 	b := &bytes.Buffer{}
@@ -178,3 +181,17 @@ func TestEncodePartContentBinary(t *testing.T) {
 	}
 	test.DiffGolden(t, b.Bytes(), "testdata", "encode", "part-bin-content.golden")
 }
+
+func TestEncodeFileModDate(t *testing.T) {
+	p := enmime.NewPart("text/plain")
+	p.Content = []byte("¡Hola, señor! Welcome to MIME")
+	p.Disposition = "inline"
+	p.FileModDate, _ = time.Parse(time.RFC822, "01 Feb 03 04:05 GMT")
+
+	b := &bytes.Buffer{}
+	err := p.Encode(b)
+	if err != nil {
+		t.Fatal(err)
+	}
+	test.DiffGolden(t, b.Bytes(), "testdata", "encode", "part-file-mod-date.golden")
+}
diff --git a/testdata/encode/part-default-headers.golden b/testdata/encode/part-default-headers.golden
index b34c422..72114b1 100644
--- a/testdata/encode/part-default-headers.golden
+++ b/testdata/encode/part-default-headers.golden
@@ -1,4 +1,5 @@
-Content-Disposition: attachment; filename=stuff.zip
+Content-Disposition: attachment; filename=stuff.zip; modification-date="01
+ Feb 03 04:05 GMT"
 Content-Id: <mycontentid>
 Content-Transfer-Encoding: base64
 Content-Type: application/zip; boundary=enmime-abcdefg0123456789;
diff --git a/testdata/encode/part-file-mod-date.golden b/testdata/encode/part-file-mod-date.golden
new file mode 100644
index 0000000..2e58916
--- /dev/null
+++ b/testdata/encode/part-file-mod-date.golden
@@ -0,0 +1,5 @@
+Content-Disposition: inline; modification-date="01 Feb 03 04:05 GMT"
+Content-Transfer-Encoding: quoted-printable
+Content-Type: text/plain; charset=utf-8
+
+=C2=A1Hola, se=C3=B1or! Welcome to MIME
diff --git a/testdata/encode/part-quoted-headers.golden b/testdata/encode/part-quoted-headers.golden
index 71cf847..34a4a5d 100644
--- a/testdata/encode/part-quoted-headers.golden
+++ b/testdata/encode/part-quoted-headers.golden
@@ -1,5 +1,5 @@
 Content-Disposition: attachment; filename="arvizturo \"x\"
- tukorfurogep.zip"
+ tukorfurogep.zip"; modification-date="01 Feb 03 04:05 GMT"
 Content-Id: <mycontentid>
 Content-Transfer-Encoding: base64
 Content-Type: application/zip; boundary=enmime-abcdefg0123456789;