2010-12-03 05:34:57 +01:00
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package multipart
import (
"bytes"
"fmt"
"io"
2011-05-20 02:18:15 +02:00
"io/ioutil"
2010-12-03 05:34:57 +01:00
"json"
"strings"
"testing"
)
func TestHorizontalWhitespace ( t * testing . T ) {
2011-05-20 02:18:15 +02:00
if ! onlyHorizontalWhitespace ( [ ] byte ( " \t" ) ) {
2010-12-03 05:34:57 +01:00
t . Error ( "expected pass" )
}
2011-05-20 02:18:15 +02:00
if onlyHorizontalWhitespace ( [ ] byte ( "foo bar" ) ) {
2010-12-03 05:34:57 +01:00
t . Error ( "expected failure" )
}
}
func TestBoundaryLine ( t * testing . T ) {
2011-09-16 17:47:21 +02:00
mr := NewReader ( strings . NewReader ( "" ) , "myBoundary" )
2011-05-20 02:18:15 +02:00
if ! mr . isBoundaryDelimiterLine ( [ ] byte ( "--myBoundary\r\n" ) ) {
2010-12-03 05:34:57 +01:00
t . Error ( "expected" )
}
2011-05-20 02:18:15 +02:00
if ! mr . isBoundaryDelimiterLine ( [ ] byte ( "--myBoundary \r\n" ) ) {
2010-12-03 05:34:57 +01:00
t . Error ( "expected" )
}
2011-05-20 02:18:15 +02:00
if ! mr . isBoundaryDelimiterLine ( [ ] byte ( "--myBoundary \n" ) ) {
2010-12-03 05:34:57 +01:00
t . Error ( "expected" )
}
2011-05-20 02:18:15 +02:00
if mr . isBoundaryDelimiterLine ( [ ] byte ( "--myBoundary bogus \n" ) ) {
2010-12-03 05:34:57 +01:00
t . Error ( "expected fail" )
}
2011-05-20 02:18:15 +02:00
if mr . isBoundaryDelimiterLine ( [ ] byte ( "--myBoundary bogus--" ) ) {
2010-12-03 05:34:57 +01:00
t . Error ( "expected fail" )
}
}
func escapeString ( v string ) string {
bytes , _ := json . Marshal ( v )
return string ( bytes )
}
func expectEq ( t * testing . T , expected , actual , what string ) {
if expected == actual {
return
}
t . Errorf ( "Unexpected value for %s; got %s (len %d) but expected: %s (len %d)" ,
what , escapeString ( actual ) , len ( actual ) , escapeString ( expected ) , len ( expected ) )
}
2011-05-20 02:18:15 +02:00
func TestNameAccessors ( t * testing . T ) {
tests := [ ... ] [ 3 ] string {
{ ` form-data; name="foo" ` , "foo" , "" } ,
{ ` form-data ; name=foo ` , "foo" , "" } ,
{ ` FORM-DATA;name="foo" ` , "foo" , "" } ,
{ ` FORM-DATA ; name="foo" ` , "foo" , "" } ,
{ ` FORM-DATA ; name="foo" ` , "foo" , "" } ,
{ ` FORM-DATA ; name=foo ` , "foo" , "" } ,
{ ` FORM-DATA ; filename="foo.txt"; name=foo; baz=quux ` , "foo" , "foo.txt" } ,
{ ` not-form-data ; filename="bar.txt"; name=foo; baz=quux ` , "" , "bar.txt" } ,
2010-12-03 05:34:57 +01:00
}
2011-05-20 02:18:15 +02:00
for i , test := range tests {
p := & Part { Header : make ( map [ string ] [ ] string ) }
2011-03-25 00:46:17 +01:00
p . Header . Set ( "Content-Disposition" , test [ 0 ] )
2011-05-20 02:18:15 +02:00
if g , e := p . FormName ( ) , test [ 1 ] ; g != e {
t . Errorf ( "test %d: FormName() = %q; want %q" , i , g , e )
}
if g , e := p . FileName ( ) , test [ 2 ] ; g != e {
t . Errorf ( "test %d: FileName() = %q; want %q" , i , g , e )
2010-12-03 05:34:57 +01:00
}
}
}
2011-05-20 02:18:15 +02:00
var longLine = strings . Repeat ( "\n\n\r\r\r\n\r\000" , ( 1 << 20 ) / 8 )
2011-09-16 17:47:21 +02:00
func testMultipartBody ( sep string ) string {
2010-12-03 05:34:57 +01:00
testBody := `
This is a multi - part message . This line is ignored .
-- MyBoundary
Header1 : value1
HEADER2 : value2
foo - bar : baz
My value
The end .
-- MyBoundary
2011-05-20 02:18:15 +02:00
name : bigsection
[ longline ]
-- MyBoundary
2010-12-03 05:34:57 +01:00
Header1 : value1b
HEADER2 : value2b
foo - bar : bazb
Line 1
Line 2
Line 3 ends in a newline , but just one .
-- MyBoundary
never read data
-- MyBoundary --
2011-05-20 02:18:15 +02:00
useless trailer
2010-12-03 05:34:57 +01:00
`
2011-09-16 17:47:21 +02:00
testBody = strings . Replace ( testBody , "\n" , sep , - 1 )
2011-05-20 02:18:15 +02:00
return strings . Replace ( testBody , "[longline]" , longLine , 1 )
}
2010-12-03 05:34:57 +01:00
2011-05-20 02:18:15 +02:00
func TestMultipart ( t * testing . T ) {
2011-09-16 17:47:21 +02:00
bodyReader := strings . NewReader ( testMultipartBody ( "\r\n" ) )
testMultipart ( t , bodyReader , false )
}
func TestMultipartOnlyNewlines ( t * testing . T ) {
bodyReader := strings . NewReader ( testMultipartBody ( "\n" ) )
testMultipart ( t , bodyReader , true )
2011-05-20 02:18:15 +02:00
}
func TestMultipartSlowInput ( t * testing . T ) {
2011-09-16 17:47:21 +02:00
bodyReader := strings . NewReader ( testMultipartBody ( "\r\n" ) )
testMultipart ( t , & slowReader { bodyReader } , false )
2011-05-20 02:18:15 +02:00
}
2011-09-16 17:47:21 +02:00
func testMultipart ( t * testing . T , r io . Reader , onlyNewlines bool ) {
2011-05-20 02:18:15 +02:00
reader := NewReader ( r , "MyBoundary" )
2010-12-03 05:34:57 +01:00
buf := new ( bytes . Buffer )
// Part1
part , err := reader . NextPart ( )
if part == nil || err != nil {
t . Error ( "Expected part1" )
return
}
2011-09-16 17:47:21 +02:00
if x := part . Header . Get ( "Header1" ) ; x != "value1" {
t . Errorf ( "part.Header.Get(%q) = %q, want %q" , "Header1" , x , "value1" )
2010-12-03 05:34:57 +01:00
}
2011-09-16 17:47:21 +02:00
if x := part . Header . Get ( "foo-bar" ) ; x != "baz" {
t . Errorf ( "part.Header.Get(%q) = %q, want %q" , "foo-bar" , x , "baz" )
2010-12-03 05:34:57 +01:00
}
2011-09-16 17:47:21 +02:00
if x := part . Header . Get ( "Foo-Bar" ) ; x != "baz" {
t . Errorf ( "part.Header.Get(%q) = %q, want %q" , "Foo-Bar" , x , "baz" )
2011-03-25 00:46:17 +01:00
}
2010-12-03 05:34:57 +01:00
buf . Reset ( )
2011-05-20 02:18:15 +02:00
if _ , err := io . Copy ( buf , part ) ; err != nil {
t . Errorf ( "part 1 copy: %v" , err )
}
2011-09-16 17:47:21 +02:00
adjustNewlines := func ( s string ) string {
if onlyNewlines {
return strings . Replace ( s , "\r\n" , "\n" , - 1 )
}
return s
}
expectEq ( t , adjustNewlines ( "My value\r\nThe end." ) , buf . String ( ) , "Value of first part" )
2010-12-03 05:34:57 +01:00
// Part2
part , err = reader . NextPart ( )
2011-05-20 02:18:15 +02:00
if err != nil {
t . Fatalf ( "Expected part2; got: %v" , err )
return
}
if e , g := "bigsection" , part . Header . Get ( "name" ) ; e != g {
t . Errorf ( "part2's name header: expected %q, got %q" , e , g )
}
buf . Reset ( )
if _ , err := io . Copy ( buf , part ) ; err != nil {
t . Errorf ( "part 2 copy: %v" , err )
}
s := buf . String ( )
if len ( s ) != len ( longLine ) {
t . Errorf ( "part2 body expected long line of length %d; got length %d" ,
len ( longLine ) , len ( s ) )
}
if s != longLine {
t . Errorf ( "part2 long body didn't match" )
}
// Part3
part , err = reader . NextPart ( )
2010-12-03 05:34:57 +01:00
if part == nil || err != nil {
2011-05-20 02:18:15 +02:00
t . Error ( "Expected part3" )
2010-12-03 05:34:57 +01:00
return
}
2011-03-25 00:46:17 +01:00
if part . Header . Get ( "foo-bar" ) != "bazb" {
2010-12-03 05:34:57 +01:00
t . Error ( "Expected foo-bar: bazb" )
}
buf . Reset ( )
2011-05-20 02:18:15 +02:00
if _ , err := io . Copy ( buf , part ) ; err != nil {
t . Errorf ( "part 3 copy: %v" , err )
}
2011-09-16 17:47:21 +02:00
expectEq ( t , adjustNewlines ( "Line 1\r\nLine 2\r\nLine 3 ends in a newline, but just one.\r\n" ) ,
2011-05-20 02:18:15 +02:00
buf . String ( ) , "body of part 3" )
2010-12-03 05:34:57 +01:00
2011-05-20 02:18:15 +02:00
// Part4
2010-12-03 05:34:57 +01:00
part , err = reader . NextPart ( )
if part == nil || err != nil {
2011-05-20 02:18:15 +02:00
t . Error ( "Expected part 4 without errors" )
2010-12-03 05:34:57 +01:00
return
}
2011-05-20 02:18:15 +02:00
// Non-existent part5
2010-12-03 05:34:57 +01:00
part , err = reader . NextPart ( )
if part != nil {
2011-05-20 02:18:15 +02:00
t . Error ( "Didn't expect a fifth part." )
2010-12-03 05:34:57 +01:00
}
2011-12-03 03:17:34 +01:00
if err != io . EOF {
2011-09-16 17:47:21 +02:00
t . Errorf ( "On fifth part expected os.EOF; got %v" , err )
2010-12-03 05:34:57 +01:00
}
}
func TestVariousTextLineEndings ( t * testing . T ) {
tests := [ ... ] string {
"Foo\nBar" ,
"Foo\nBar\n" ,
"Foo\r\nBar" ,
"Foo\r\nBar\r\n" ,
"Foo\rBar" ,
"Foo\rBar\r" ,
"\x00\x01\x02\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10" ,
}
for testNum , expectedBody := range tests {
body := "--BOUNDARY\r\n" +
"Content-Disposition: form-data; name=\"value\"\r\n" +
"\r\n" +
expectedBody +
"\r\n--BOUNDARY--\r\n"
bodyReader := strings . NewReader ( body )
reader := NewReader ( bodyReader , "BOUNDARY" )
buf := new ( bytes . Buffer )
part , err := reader . NextPart ( )
if part == nil {
t . Errorf ( "Expected a body part on text %d" , testNum )
continue
}
if err != nil {
t . Errorf ( "Unexpected error on text %d: %v" , testNum , err )
continue
}
written , err := io . Copy ( buf , part )
expectEq ( t , expectedBody , buf . String ( ) , fmt . Sprintf ( "test %d" , testNum ) )
if err != nil {
t . Errorf ( "Error copying multipart; bytes=%v, error=%v" , written , err )
}
part , err = reader . NextPart ( )
if part != nil {
t . Errorf ( "Unexpected part in test %d" , testNum )
}
2011-12-03 03:17:34 +01:00
if err != io . EOF {
2011-05-20 02:18:15 +02:00
t . Errorf ( "On test %d expected os.EOF; got %v" , testNum , err )
2010-12-03 05:34:57 +01:00
}
}
}
2011-05-20 02:18:15 +02:00
type maliciousReader struct {
t * testing . T
n int
}
const maxReadThreshold = 1 << 20
2011-12-03 03:17:34 +01:00
func ( mr * maliciousReader ) Read ( b [ ] byte ) ( n int , err error ) {
2011-05-20 02:18:15 +02:00
mr . n += len ( b )
if mr . n >= maxReadThreshold {
mr . t . Fatal ( "too much was read" )
2011-12-03 03:17:34 +01:00
return 0 , io . EOF
2011-05-20 02:18:15 +02:00
}
return len ( b ) , nil
}
func TestLineLimit ( t * testing . T ) {
mr := & maliciousReader { t : t }
r := NewReader ( mr , "fooBoundary" )
part , err := r . NextPart ( )
if part != nil {
t . Errorf ( "unexpected part read" )
}
if err == nil {
t . Errorf ( "expected an error" )
}
if mr . n >= maxReadThreshold {
t . Errorf ( "expected to read < %d bytes; read %d" , maxReadThreshold , mr . n )
}
}
func TestMultipartTruncated ( t * testing . T ) {
testBody := `
This is a multi - part message . This line is ignored .
-- MyBoundary
foo - bar : baz
Oh no , premature EOF !
`
body := strings . Replace ( testBody , "\n" , "\r\n" , - 1 )
bodyReader := strings . NewReader ( body )
r := NewReader ( bodyReader , "MyBoundary" )
part , err := r . NextPart ( )
if err != nil {
t . Fatalf ( "didn't get a part" )
}
_ , err = io . Copy ( ioutil . Discard , part )
if err != io . ErrUnexpectedEOF {
t . Fatalf ( "expected error io.ErrUnexpectedEOF; got %v" , err )
}
}
2011-09-16 17:47:21 +02:00
func TestZeroLengthBody ( t * testing . T ) {
testBody := strings . Replace ( `
This is a multi - part message . This line is ignored .
-- MyBoundary
foo : bar
-- MyBoundary --
` , "\n" , "\r\n" , - 1 )
r := NewReader ( strings . NewReader ( testBody ) , "MyBoundary" )
part , err := r . NextPart ( )
if err != nil {
t . Fatalf ( "didn't get a part" )
}
n , err := io . Copy ( ioutil . Discard , part )
if err != nil {
t . Errorf ( "error reading part: %v" , err )
}
if n != 0 {
t . Errorf ( "read %d bytes; expected 0" , n )
}
}
2011-05-20 02:18:15 +02:00
type slowReader struct {
r io . Reader
}
2011-12-03 03:17:34 +01:00
func ( s * slowReader ) Read ( p [ ] byte ) ( int , error ) {
2011-05-20 02:18:15 +02:00
if len ( p ) == 0 {
return s . r . Read ( p )
}
return s . r . Read ( p [ : 1 ] )
}
2011-09-16 17:47:21 +02:00
func TestLineContinuation ( t * testing . T ) {
// This body, extracted from an email, contains headers that span multiple
// lines.
// TODO: The original mail ended with a double-newline before the
// final delimiter; this was manually edited to use a CRLF.
testBody :=
"\n--Apple-Mail-2-292336769\nContent-Transfer-Encoding: 7bit\nContent-Type: text/plain;\n\tcharset=US-ASCII;\n\tdelsp=yes;\n\tformat=flowed\n\nI'm finding the same thing happening on my system (10.4.1).\n\n\n--Apple-Mail-2-292336769\nContent-Transfer-Encoding: quoted-printable\nContent-Type: text/html;\n\tcharset=ISO-8859-1\n\n<HTML><BODY>I'm finding the same thing =\nhappening on my system (10.4.1).=A0 But I built it with XCode =\n2.0.</BODY></=\nHTML>=\n\r\n--Apple-Mail-2-292336769--\n"
r := NewReader ( strings . NewReader ( testBody ) , "Apple-Mail-2-292336769" )
for i := 0 ; i < 2 ; i ++ {
part , err := r . NextPart ( )
if err != nil {
t . Fatalf ( "didn't get a part" )
}
n , err := io . Copy ( ioutil . Discard , part )
if err != nil {
t . Errorf ( "error reading part: %v" , err )
}
if n <= 0 {
t . Errorf ( "read %d bytes; expected >0" , n )
}
}
}