1 // Copyright 2012 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
22 func TestQuotedPrintable(t
*testing
.T
) {
28 {in
: "foo bar", want
: "foo bar"},
29 {in
: "foo bar=3D", want
: "foo bar="},
30 {in
: "foo bar=\n", want
: "foo bar"},
31 {in
: "foo bar\n", want
: "foo bar\n"}, // somewhat lax.
32 {in
: "foo bar=0", want
: "foo bar", err
: io
.ErrUnexpectedEOF
},
33 {in
: "foo bar=ab", want
: "foo bar", err
: "multipart: invalid quoted-printable hex byte 0x61"},
34 {in
: "foo bar=0D=0A", want
: "foo bar\r\n"},
35 {in
: " A B \r\n C ", want
: " A B\r\n C"},
36 {in
: " A B =\r\n C ", want
: " A B C"},
37 {in
: " A B =\n C ", want
: " A B C"}, // lax. treating LF as CRLF
38 {in
: "foo=\nbar", want
: "foobar"},
39 {in
: "foo\x00bar", want
: "foo", err
: "multipart: invalid unescaped byte 0x00 in quoted-printable body"},
40 {in
: "foo bar\xff", want
: "foo bar", err
: "multipart: invalid unescaped byte 0xff in quoted-printable body"},
43 {in
: "=3D30\n", want
: "=30\n"},
44 {in
: "=00=FF0=\n", want
: "\x00\xff0"},
46 // Trailing whitespace
47 {in
: "foo \n", want
: "foo\n"},
48 {in
: "foo \n\nfoo =\n\nfoo=20\n\n", want
: "foo\n\nfoo \nfoo \n\n"},
50 // Tests that we allow bare \n and \r through, despite it being strictly
51 // not permitted per RFC 2045, Section 6.7 Page 22 bullet (4).
52 {in
: "foo\nbar", want
: "foo\nbar"},
53 {in
: "foo\rbar", want
: "foo\rbar"},
54 {in
: "foo\r\nbar", want
: "foo\r\nbar"},
56 // Different types of soft line-breaks.
57 {in
: "foo=\r\nbar", want
: "foobar"},
58 {in
: "foo=\nbar", want
: "foobar"},
59 {in
: "foo=\rbar", want
: "foo", err
: "multipart: invalid quoted-printable hex byte 0x0d"},
60 {in
: "foo=\r\r\r \nbar", want
: "foo", err
: `multipart: invalid bytes after =: "\r\r\r \n"`},
62 // Example from RFC 2045:
63 {in
: "Now's the time =\n" + "for all folk to come=\n" + " to the aid of their country.",
64 want
: "Now's the time for all folk to come to the aid of their country."},
66 for _
, tt
:= range tests
{
68 _
, err
:= io
.Copy(&buf
, newQuotedPrintableReader(strings
.NewReader(tt
.in
)))
69 if got
:= buf
.String(); got
!= tt
.want
{
70 t
.Errorf("for %q, got %q; want %q", tt
.in
, got
, tt
.want
)
72 switch verr
:= tt
.err
.(type) {
75 t
.Errorf("for %q, got unexpected error: %v", tt
.in
, err
)
78 if got
:= fmt
.Sprint(err
); got
!= verr
{
79 t
.Errorf("for %q, got error %q; want %q", tt
.in
, got
, verr
)
83 t
.Errorf("for %q, got error %q; want %q", tt
.in
, err
, verr
)
90 func everySequence(base
, alpha
string, length
int, fn
func(string)) {
91 if len(base
) == length
{
95 for i
:= 0; i
< len(alpha
); i
++ {
96 everySequence(base
+alpha
[i
:i
+1], alpha
, length
, fn
)
100 var useQprint
= flag
.Bool("qprint", false, "Compare against the 'qprint' program.")
102 var badSoftRx
= regexp
.MustCompile(`=([^\r\n]+?\n)|([^\r\n]+$)|(\r$)|(\r[^\n]+\n)|( \r\n)`)
104 func TestQPExhaustive(t
*testing
.T
) {
106 _
, err
:= exec
.LookPath("qprint")
108 t
.Fatalf("Error looking for qprint: %v", err
)
113 res
:= make(map[string]int)
114 everySequence("", "0A \r\n=", 6, func(s
string) {
115 if strings
.HasSuffix(s
, "=") || strings
.Contains(s
, "==") {
119 _
, err
:= io
.Copy(&buf
, newQuotedPrintableReader(strings
.NewReader(s
)))
121 errStr
:= err
.Error()
122 if strings
.Contains(errStr
, "invalid bytes after =:") {
123 errStr
= "invalid bytes after ="
126 if strings
.Contains(errStr
, "invalid quoted-printable hex byte ") {
127 if strings
.HasSuffix(errStr
, "0x20") && (strings
.Contains(s
, "=0 ") || strings
.Contains(s
, "=A ") || strings
.Contains(s
, "= ")) {
130 if strings
.HasSuffix(errStr
, "0x3d") && (strings
.Contains(s
, "=0=") || strings
.Contains(s
, "=A=")) {
133 if strings
.HasSuffix(errStr
, "0x0a") || strings
.HasSuffix(errStr
, "0x0d") {
134 // bunch of cases; since whitespace at the end of a line before \n is removed.
138 if strings
.Contains(errStr
, "unexpected EOF") {
141 if errStr
== "invalid bytes after =" && badSoftRx
.MatchString(s
) {
144 t
.Errorf("decode(%q) = %v", s
, err
)
148 cmd
:= exec
.Command("qprint", "-d")
149 cmd
.Stdin
= strings
.NewReader(s
)
150 stderr
, err
:= cmd
.StderrPipe()
154 qpres
:= make(chan interface{}, 2)
156 br
:= bufio
.NewReader(stderr
)
157 s
, _
:= br
.ReadString('\n')
159 qpres
<- errors
.New(s
)
160 if cmd
.Process
!= nil {
161 // It can get stuck on invalid input, like:
162 // echo -n "0000= " | qprint -d
168 want
, err
:= cmd
.Output()
175 if want
, ok
:= got
.([]byte); ok
{
176 if string(want
) != buf
.String() {
177 t
.Errorf("go decode(%q) = %q; qprint = %q", s
, want
, buf
.String())
180 t
.Logf("qprint -d(%q) = %v", s
, got
)
182 case <-time
.After(5 * time
.Second
):
183 t
.Logf("qprint timeout on %q", s
)
188 var outcomes
[]string
189 for k
, v
:= range res
{
190 outcomes
= append(outcomes
, fmt
.Sprintf("%v: %d", k
, v
))
192 sort
.Strings(outcomes
)
193 got
:= strings
.Join(outcomes
, "\n")
195 invalid bytes after =: 3397
196 multipart: invalid quoted-printable hex byte 0x0a: 1400
197 multipart: invalid quoted-printable hex byte 0x0d: 2700
198 multipart: invalid quoted-printable hex byte 0x20: 2490
199 multipart: invalid quoted-printable hex byte 0x3d: 440
200 unexpected EOF: 3122`
202 t
.Errorf("Got:\n%s\nWant:\n%s", got
, want
)