mirror of https://github.com/veypi/OneAuth.git
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			753 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
		
		
			
		
	
	
			753 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			Go
		
	
| 
											1 year ago
										 | // Copyright 2009 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 xml | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"bytes" | ||
|  | 	"fmt" | ||
|  | 	"io" | ||
|  | 	"reflect" | ||
|  | 	"strings" | ||
|  | 	"testing" | ||
|  | 	"unicode/utf8" | ||
|  | ) | ||
|  | 
 | ||
|  | const testInput = ` | ||
|  | <?xml version="1.0" encoding="UTF-8"?> | ||
|  | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | ||
|  |   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> | ||
|  | <body xmlns:foo="ns1" xmlns="ns2" xmlns:tag="ns3" ` + | ||
|  | 	"\r\n\t" + `  > | ||
|  |   <hello lang="en">World <>'" 白鵬翔</hello> | ||
|  |   <query>&何; &is-it;</query> | ||
|  |   <goodbye /> | ||
|  |   <outer foo:attr="value" xmlns:tag="ns4"> | ||
|  |     <inner/> | ||
|  |   </outer> | ||
|  |   <tag:name> | ||
|  |     <![CDATA[Some text here.]]> | ||
|  |   </tag:name> | ||
|  | </body><!-- missing final newline -->` | ||
|  | 
 | ||
|  | var testEntity = map[string]string{"何": "What", "is-it": "is it?"} | ||
|  | 
 | ||
|  | var rawTokens = []Token{ | ||
|  | 	CharData("\n"), | ||
|  | 	ProcInst{"xml", []byte(`version="1.0" encoding="UTF-8"`)}, | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | ||
|  |   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"`), | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "body"}, []Attr{{Name{"xmlns", "foo"}, "ns1"}, {Name{"", "xmlns"}, "ns2"}, {Name{"xmlns", "tag"}, "ns3"}}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"", "hello"}, []Attr{{Name{"", "lang"}, "en"}}}, | ||
|  | 	CharData("World <>'\" 白鵬翔"), | ||
|  | 	EndElement{Name{"", "hello"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"", "query"}, []Attr{}}, | ||
|  | 	CharData("What is it?"), | ||
|  | 	EndElement{Name{"", "query"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"", "goodbye"}, []Attr{}}, | ||
|  | 	EndElement{Name{"", "goodbye"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"", "outer"}, []Attr{{Name{"foo", "attr"}, "value"}, {Name{"xmlns", "tag"}, "ns4"}}}, | ||
|  | 	CharData("\n    "), | ||
|  | 	StartElement{Name{"", "inner"}, []Attr{}}, | ||
|  | 	EndElement{Name{"", "inner"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	EndElement{Name{"", "outer"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"tag", "name"}, []Attr{}}, | ||
|  | 	CharData("\n    "), | ||
|  | 	CharData("Some text here."), | ||
|  | 	CharData("\n  "), | ||
|  | 	EndElement{Name{"tag", "name"}}, | ||
|  | 	CharData("\n"), | ||
|  | 	EndElement{Name{"", "body"}}, | ||
|  | 	Comment(" missing final newline "), | ||
|  | } | ||
|  | 
 | ||
|  | var cookedTokens = []Token{ | ||
|  | 	CharData("\n"), | ||
|  | 	ProcInst{"xml", []byte(`version="1.0" encoding="UTF-8"`)}, | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" | ||
|  |   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"`), | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"ns2", "body"}, []Attr{{Name{"xmlns", "foo"}, "ns1"}, {Name{"", "xmlns"}, "ns2"}, {Name{"xmlns", "tag"}, "ns3"}}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"ns2", "hello"}, []Attr{{Name{"", "lang"}, "en"}}}, | ||
|  | 	CharData("World <>'\" 白鵬翔"), | ||
|  | 	EndElement{Name{"ns2", "hello"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"ns2", "query"}, []Attr{}}, | ||
|  | 	CharData("What is it?"), | ||
|  | 	EndElement{Name{"ns2", "query"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"ns2", "goodbye"}, []Attr{}}, | ||
|  | 	EndElement{Name{"ns2", "goodbye"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"ns2", "outer"}, []Attr{{Name{"ns1", "attr"}, "value"}, {Name{"xmlns", "tag"}, "ns4"}}}, | ||
|  | 	CharData("\n    "), | ||
|  | 	StartElement{Name{"ns2", "inner"}, []Attr{}}, | ||
|  | 	EndElement{Name{"ns2", "inner"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	EndElement{Name{"ns2", "outer"}}, | ||
|  | 	CharData("\n  "), | ||
|  | 	StartElement{Name{"ns3", "name"}, []Attr{}}, | ||
|  | 	CharData("\n    "), | ||
|  | 	CharData("Some text here."), | ||
|  | 	CharData("\n  "), | ||
|  | 	EndElement{Name{"ns3", "name"}}, | ||
|  | 	CharData("\n"), | ||
|  | 	EndElement{Name{"ns2", "body"}}, | ||
|  | 	Comment(" missing final newline "), | ||
|  | } | ||
|  | 
 | ||
|  | const testInputAltEncoding = ` | ||
|  | <?xml version="1.0" encoding="x-testing-uppercase"?> | ||
|  | <TAG>VALUE</TAG>` | ||
|  | 
 | ||
|  | var rawTokensAltEncoding = []Token{ | ||
|  | 	CharData("\n"), | ||
|  | 	ProcInst{"xml", []byte(`version="1.0" encoding="x-testing-uppercase"`)}, | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "tag"}, []Attr{}}, | ||
|  | 	CharData("value"), | ||
|  | 	EndElement{Name{"", "tag"}}, | ||
|  | } | ||
|  | 
 | ||
|  | var xmlInput = []string{ | ||
|  | 	// unexpected EOF cases
 | ||
|  | 	"<", | ||
|  | 	"<t", | ||
|  | 	"<t ", | ||
|  | 	"<t/", | ||
|  | 	"<!", | ||
|  | 	"<!-", | ||
|  | 	"<!--", | ||
|  | 	"<!--c-", | ||
|  | 	"<!--c--", | ||
|  | 	"<!d", | ||
|  | 	"<t></", | ||
|  | 	"<t></t", | ||
|  | 	"<?", | ||
|  | 	"<?p", | ||
|  | 	"<t a", | ||
|  | 	"<t a=", | ||
|  | 	"<t a='", | ||
|  | 	"<t a=''", | ||
|  | 	"<t/><![", | ||
|  | 	"<t/><![C", | ||
|  | 	"<t/><![CDATA[d", | ||
|  | 	"<t/><![CDATA[d]", | ||
|  | 	"<t/><![CDATA[d]]", | ||
|  | 
 | ||
|  | 	// other Syntax errors
 | ||
|  | 	"<>", | ||
|  | 	"<t/a", | ||
|  | 	"<0 />", | ||
|  | 	"<?0 >", | ||
|  | 	//	"<!0 >",	// let the Token() caller handle
 | ||
|  | 	"</0>", | ||
|  | 	"<t 0=''>", | ||
|  | 	"<t a='&'>", | ||
|  | 	"<t a='<'>", | ||
|  | 	"<t> c;</t>", | ||
|  | 	"<t a>", | ||
|  | 	"<t a=>", | ||
|  | 	"<t a=v>", | ||
|  | 	//	"<![CDATA[d]]>",	// let the Token() caller handle
 | ||
|  | 	"<t></e>", | ||
|  | 	"<t></>", | ||
|  | 	"<t></t!", | ||
|  | 	"<t>cdata]]></t>", | ||
|  | } | ||
|  | 
 | ||
|  | func TestRawToken(t *testing.T) { | ||
|  | 	d := NewDecoder(strings.NewReader(testInput)) | ||
|  | 	d.Entity = testEntity | ||
|  | 	testRawToken(t, d, testInput, rawTokens) | ||
|  | } | ||
|  | 
 | ||
|  | const nonStrictInput = ` | ||
|  | <tag>non&entity</tag> | ||
|  | <tag>&unknown;entity</tag> | ||
|  | <tag>{</tag> | ||
|  | <tag>&#zzz;</tag> | ||
|  | <tag>&なまえ3;</tag> | ||
|  | <tag><-gt;</tag> | ||
|  | <tag>&;</tag> | ||
|  | <tag>&0a;</tag> | ||
|  | ` | ||
|  | 
 | ||
|  | var nonStringEntity = map[string]string{"": "oops!", "0a": "oops!"} | ||
|  | 
 | ||
|  | var nonStrictTokens = []Token{ | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "tag"}, []Attr{}}, | ||
|  | 	CharData("non&entity"), | ||
|  | 	EndElement{Name{"", "tag"}}, | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "tag"}, []Attr{}}, | ||
|  | 	CharData("&unknown;entity"), | ||
|  | 	EndElement{Name{"", "tag"}}, | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "tag"}, []Attr{}}, | ||
|  | 	CharData("{"), | ||
|  | 	EndElement{Name{"", "tag"}}, | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "tag"}, []Attr{}}, | ||
|  | 	CharData("&#zzz;"), | ||
|  | 	EndElement{Name{"", "tag"}}, | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "tag"}, []Attr{}}, | ||
|  | 	CharData("&なまえ3;"), | ||
|  | 	EndElement{Name{"", "tag"}}, | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "tag"}, []Attr{}}, | ||
|  | 	CharData("<-gt;"), | ||
|  | 	EndElement{Name{"", "tag"}}, | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "tag"}, []Attr{}}, | ||
|  | 	CharData("&;"), | ||
|  | 	EndElement{Name{"", "tag"}}, | ||
|  | 	CharData("\n"), | ||
|  | 	StartElement{Name{"", "tag"}, []Attr{}}, | ||
|  | 	CharData("&0a;"), | ||
|  | 	EndElement{Name{"", "tag"}}, | ||
|  | 	CharData("\n"), | ||
|  | } | ||
|  | 
 | ||
|  | func TestNonStrictRawToken(t *testing.T) { | ||
|  | 	d := NewDecoder(strings.NewReader(nonStrictInput)) | ||
|  | 	d.Strict = false | ||
|  | 	testRawToken(t, d, nonStrictInput, nonStrictTokens) | ||
|  | } | ||
|  | 
 | ||
|  | type downCaser struct { | ||
|  | 	t *testing.T | ||
|  | 	r io.ByteReader | ||
|  | } | ||
|  | 
 | ||
|  | func (d *downCaser) ReadByte() (c byte, err error) { | ||
|  | 	c, err = d.r.ReadByte() | ||
|  | 	if c >= 'A' && c <= 'Z' { | ||
|  | 		c += 'a' - 'A' | ||
|  | 	} | ||
|  | 	return | ||
|  | } | ||
|  | 
 | ||
|  | func (d *downCaser) Read(p []byte) (int, error) { | ||
|  | 	d.t.Fatalf("unexpected Read call on downCaser reader") | ||
|  | 	panic("unreachable") | ||
|  | } | ||
|  | 
 | ||
|  | func TestRawTokenAltEncoding(t *testing.T) { | ||
|  | 	d := NewDecoder(strings.NewReader(testInputAltEncoding)) | ||
|  | 	d.CharsetReader = func(charset string, input io.Reader) (io.Reader, error) { | ||
|  | 		if charset != "x-testing-uppercase" { | ||
|  | 			t.Fatalf("unexpected charset %q", charset) | ||
|  | 		} | ||
|  | 		return &downCaser{t, input.(io.ByteReader)}, nil | ||
|  | 	} | ||
|  | 	testRawToken(t, d, testInputAltEncoding, rawTokensAltEncoding) | ||
|  | } | ||
|  | 
 | ||
|  | func TestRawTokenAltEncodingNoConverter(t *testing.T) { | ||
|  | 	d := NewDecoder(strings.NewReader(testInputAltEncoding)) | ||
|  | 	token, err := d.RawToken() | ||
|  | 	if token == nil { | ||
|  | 		t.Fatalf("expected a token on first RawToken call") | ||
|  | 	} | ||
|  | 	if err != nil { | ||
|  | 		t.Fatal(err) | ||
|  | 	} | ||
|  | 	token, err = d.RawToken() | ||
|  | 	if token != nil { | ||
|  | 		t.Errorf("expected a nil token; got %#v", token) | ||
|  | 	} | ||
|  | 	if err == nil { | ||
|  | 		t.Fatalf("expected an error on second RawToken call") | ||
|  | 	} | ||
|  | 	const encoding = "x-testing-uppercase" | ||
|  | 	if !strings.Contains(err.Error(), encoding) { | ||
|  | 		t.Errorf("expected error to contain %q; got error: %v", | ||
|  | 			encoding, err) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func testRawToken(t *testing.T, d *Decoder, raw string, rawTokens []Token) { | ||
|  | 	lastEnd := int64(0) | ||
|  | 	for i, want := range rawTokens { | ||
|  | 		start := d.InputOffset() | ||
|  | 		have, err := d.RawToken() | ||
|  | 		end := d.InputOffset() | ||
|  | 		if err != nil { | ||
|  | 			t.Fatalf("token %d: unexpected error: %s", i, err) | ||
|  | 		} | ||
|  | 		if !reflect.DeepEqual(have, want) { | ||
|  | 			var shave, swant string | ||
|  | 			if _, ok := have.(CharData); ok { | ||
|  | 				shave = fmt.Sprintf("CharData(%q)", have) | ||
|  | 			} else { | ||
|  | 				shave = fmt.Sprintf("%#v", have) | ||
|  | 			} | ||
|  | 			if _, ok := want.(CharData); ok { | ||
|  | 				swant = fmt.Sprintf("CharData(%q)", want) | ||
|  | 			} else { | ||
|  | 				swant = fmt.Sprintf("%#v", want) | ||
|  | 			} | ||
|  | 			t.Errorf("token %d = %s, want %s", i, shave, swant) | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Check that InputOffset returned actual token.
 | ||
|  | 		switch { | ||
|  | 		case start < lastEnd: | ||
|  | 			t.Errorf("token %d: position [%d,%d) for %T is before previous token", i, start, end, have) | ||
|  | 		case start >= end: | ||
|  | 			// Special case: EndElement can be synthesized.
 | ||
|  | 			if start == end && end == lastEnd { | ||
|  | 				break | ||
|  | 			} | ||
|  | 			t.Errorf("token %d: position [%d,%d) for %T is empty", i, start, end, have) | ||
|  | 		case end > int64(len(raw)): | ||
|  | 			t.Errorf("token %d: position [%d,%d) for %T extends beyond input", i, start, end, have) | ||
|  | 		default: | ||
|  | 			text := raw[start:end] | ||
|  | 			if strings.ContainsAny(text, "<>") && (!strings.HasPrefix(text, "<") || !strings.HasSuffix(text, ">")) { | ||
|  | 				t.Errorf("token %d: misaligned raw token %#q for %T", i, text, have) | ||
|  | 			} | ||
|  | 		} | ||
|  | 		lastEnd = end | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // Ensure that directives (specifically !DOCTYPE) include the complete
 | ||
|  | // text of any nested directives, noting that < and > do not change
 | ||
|  | // nesting depth if they are in single or double quotes.
 | ||
|  | 
 | ||
|  | var nestedDirectivesInput = ` | ||
|  | <!DOCTYPE [<!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">]> | ||
|  | <!DOCTYPE [<!ENTITY xlt ">">]> | ||
|  | <!DOCTYPE [<!ENTITY xlt "<">]> | ||
|  | <!DOCTYPE [<!ENTITY xlt '>'>]> | ||
|  | <!DOCTYPE [<!ENTITY xlt '<'>]> | ||
|  | <!DOCTYPE [<!ENTITY xlt '">'>]> | ||
|  | <!DOCTYPE [<!ENTITY xlt "'<">]> | ||
|  | ` | ||
|  | 
 | ||
|  | var nestedDirectivesTokens = []Token{ | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE [<!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">]`), | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE [<!ENTITY xlt ">">]`), | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE [<!ENTITY xlt "<">]`), | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE [<!ENTITY xlt '>'>]`), | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE [<!ENTITY xlt '<'>]`), | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE [<!ENTITY xlt '">'>]`), | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE [<!ENTITY xlt "'<">]`), | ||
|  | 	CharData("\n"), | ||
|  | } | ||
|  | 
 | ||
|  | func TestNestedDirectives(t *testing.T) { | ||
|  | 	d := NewDecoder(strings.NewReader(nestedDirectivesInput)) | ||
|  | 
 | ||
|  | 	for i, want := range nestedDirectivesTokens { | ||
|  | 		have, err := d.Token() | ||
|  | 		if err != nil { | ||
|  | 			t.Fatalf("token %d: unexpected error: %s", i, err) | ||
|  | 		} | ||
|  | 		if !reflect.DeepEqual(have, want) { | ||
|  | 			t.Errorf("token %d = %#v want %#v", i, have, want) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestToken(t *testing.T) { | ||
|  | 	d := NewDecoder(strings.NewReader(testInput)) | ||
|  | 	d.Entity = testEntity | ||
|  | 
 | ||
|  | 	for i, want := range cookedTokens { | ||
|  | 		have, err := d.Token() | ||
|  | 		if err != nil { | ||
|  | 			t.Fatalf("token %d: unexpected error: %s", i, err) | ||
|  | 		} | ||
|  | 		if !reflect.DeepEqual(have, want) { | ||
|  | 			t.Errorf("token %d = %#v want %#v", i, have, want) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestSyntax(t *testing.T) { | ||
|  | 	for i := range xmlInput { | ||
|  | 		d := NewDecoder(strings.NewReader(xmlInput[i])) | ||
|  | 		var err error | ||
|  | 		for _, err = d.Token(); err == nil; _, err = d.Token() { | ||
|  | 		} | ||
|  | 		if _, ok := err.(*SyntaxError); !ok { | ||
|  | 			t.Fatalf(`xmlInput "%s": expected SyntaxError not received`, xmlInput[i]) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | type allScalars struct { | ||
|  | 	True1     bool | ||
|  | 	True2     bool | ||
|  | 	False1    bool | ||
|  | 	False2    bool | ||
|  | 	Int       int | ||
|  | 	Int8      int8 | ||
|  | 	Int16     int16 | ||
|  | 	Int32     int32 | ||
|  | 	Int64     int64 | ||
|  | 	Uint      int | ||
|  | 	Uint8     uint8 | ||
|  | 	Uint16    uint16 | ||
|  | 	Uint32    uint32 | ||
|  | 	Uint64    uint64 | ||
|  | 	Uintptr   uintptr | ||
|  | 	Float32   float32 | ||
|  | 	Float64   float64 | ||
|  | 	String    string | ||
|  | 	PtrString *string | ||
|  | } | ||
|  | 
 | ||
|  | var all = allScalars{ | ||
|  | 	True1:     true, | ||
|  | 	True2:     true, | ||
|  | 	False1:    false, | ||
|  | 	False2:    false, | ||
|  | 	Int:       1, | ||
|  | 	Int8:      -2, | ||
|  | 	Int16:     3, | ||
|  | 	Int32:     -4, | ||
|  | 	Int64:     5, | ||
|  | 	Uint:      6, | ||
|  | 	Uint8:     7, | ||
|  | 	Uint16:    8, | ||
|  | 	Uint32:    9, | ||
|  | 	Uint64:    10, | ||
|  | 	Uintptr:   11, | ||
|  | 	Float32:   13.0, | ||
|  | 	Float64:   14.0, | ||
|  | 	String:    "15", | ||
|  | 	PtrString: &sixteen, | ||
|  | } | ||
|  | 
 | ||
|  | var sixteen = "16" | ||
|  | 
 | ||
|  | const testScalarsInput = `<allscalars> | ||
|  | 	<True1>true</True1> | ||
|  | 	<True2>1</True2> | ||
|  | 	<False1>false</False1> | ||
|  | 	<False2>0</False2> | ||
|  | 	<Int>1</Int> | ||
|  | 	<Int8>-2</Int8> | ||
|  | 	<Int16>3</Int16> | ||
|  | 	<Int32>-4</Int32> | ||
|  | 	<Int64>5</Int64> | ||
|  | 	<Uint>6</Uint> | ||
|  | 	<Uint8>7</Uint8> | ||
|  | 	<Uint16>8</Uint16> | ||
|  | 	<Uint32>9</Uint32> | ||
|  | 	<Uint64>10</Uint64> | ||
|  | 	<Uintptr>11</Uintptr> | ||
|  | 	<Float>12.0</Float> | ||
|  | 	<Float32>13.0</Float32> | ||
|  | 	<Float64>14.0</Float64> | ||
|  | 	<String>15</String> | ||
|  | 	<PtrString>16</PtrString> | ||
|  | </allscalars>` | ||
|  | 
 | ||
|  | func TestAllScalars(t *testing.T) { | ||
|  | 	var a allScalars | ||
|  | 	err := Unmarshal([]byte(testScalarsInput), &a) | ||
|  | 
 | ||
|  | 	if err != nil { | ||
|  | 		t.Fatal(err) | ||
|  | 	} | ||
|  | 	if !reflect.DeepEqual(a, all) { | ||
|  | 		t.Errorf("have %+v want %+v", a, all) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | type item struct { | ||
|  | 	Field_a string | ||
|  | } | ||
|  | 
 | ||
|  | func TestIssue569(t *testing.T) { | ||
|  | 	data := `<item><Field_a>abcd</Field_a></item>` | ||
|  | 	var i item | ||
|  | 	err := Unmarshal([]byte(data), &i) | ||
|  | 
 | ||
|  | 	if err != nil || i.Field_a != "abcd" { | ||
|  | 		t.Fatal("Expecting abcd") | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestUnquotedAttrs(t *testing.T) { | ||
|  | 	data := "<tag attr=azAZ09:-_\t>" | ||
|  | 	d := NewDecoder(strings.NewReader(data)) | ||
|  | 	d.Strict = false | ||
|  | 	token, err := d.Token() | ||
|  | 	if _, ok := err.(*SyntaxError); ok { | ||
|  | 		t.Errorf("Unexpected error: %v", err) | ||
|  | 	} | ||
|  | 	if token.(StartElement).Name.Local != "tag" { | ||
|  | 		t.Errorf("Unexpected tag name: %v", token.(StartElement).Name.Local) | ||
|  | 	} | ||
|  | 	attr := token.(StartElement).Attr[0] | ||
|  | 	if attr.Value != "azAZ09:-_" { | ||
|  | 		t.Errorf("Unexpected attribute value: %v", attr.Value) | ||
|  | 	} | ||
|  | 	if attr.Name.Local != "attr" { | ||
|  | 		t.Errorf("Unexpected attribute name: %v", attr.Name.Local) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestValuelessAttrs(t *testing.T) { | ||
|  | 	tests := [][3]string{ | ||
|  | 		{"<p nowrap>", "p", "nowrap"}, | ||
|  | 		{"<p nowrap >", "p", "nowrap"}, | ||
|  | 		{"<input checked/>", "input", "checked"}, | ||
|  | 		{"<input checked />", "input", "checked"}, | ||
|  | 	} | ||
|  | 	for _, test := range tests { | ||
|  | 		d := NewDecoder(strings.NewReader(test[0])) | ||
|  | 		d.Strict = false | ||
|  | 		token, err := d.Token() | ||
|  | 		if _, ok := err.(*SyntaxError); ok { | ||
|  | 			t.Errorf("Unexpected error: %v", err) | ||
|  | 		} | ||
|  | 		if token.(StartElement).Name.Local != test[1] { | ||
|  | 			t.Errorf("Unexpected tag name: %v", token.(StartElement).Name.Local) | ||
|  | 		} | ||
|  | 		attr := token.(StartElement).Attr[0] | ||
|  | 		if attr.Value != test[2] { | ||
|  | 			t.Errorf("Unexpected attribute value: %v", attr.Value) | ||
|  | 		} | ||
|  | 		if attr.Name.Local != test[2] { | ||
|  | 			t.Errorf("Unexpected attribute name: %v", attr.Name.Local) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestCopyTokenCharData(t *testing.T) { | ||
|  | 	data := []byte("same data") | ||
|  | 	var tok1 Token = CharData(data) | ||
|  | 	tok2 := CopyToken(tok1) | ||
|  | 	if !reflect.DeepEqual(tok1, tok2) { | ||
|  | 		t.Error("CopyToken(CharData) != CharData") | ||
|  | 	} | ||
|  | 	data[1] = 'o' | ||
|  | 	if reflect.DeepEqual(tok1, tok2) { | ||
|  | 		t.Error("CopyToken(CharData) uses same buffer.") | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestCopyTokenStartElement(t *testing.T) { | ||
|  | 	elt := StartElement{Name{"", "hello"}, []Attr{{Name{"", "lang"}, "en"}}} | ||
|  | 	var tok1 Token = elt | ||
|  | 	tok2 := CopyToken(tok1) | ||
|  | 	if tok1.(StartElement).Attr[0].Value != "en" { | ||
|  | 		t.Error("CopyToken overwrote Attr[0]") | ||
|  | 	} | ||
|  | 	if !reflect.DeepEqual(tok1, tok2) { | ||
|  | 		t.Error("CopyToken(StartElement) != StartElement") | ||
|  | 	} | ||
|  | 	tok1.(StartElement).Attr[0] = Attr{Name{"", "lang"}, "de"} | ||
|  | 	if reflect.DeepEqual(tok1, tok2) { | ||
|  | 		t.Error("CopyToken(CharData) uses same buffer.") | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestSyntaxErrorLineNum(t *testing.T) { | ||
|  | 	testInput := "<P>Foo<P>\n\n<P>Bar</>\n" | ||
|  | 	d := NewDecoder(strings.NewReader(testInput)) | ||
|  | 	var err error | ||
|  | 	for _, err = d.Token(); err == nil; _, err = d.Token() { | ||
|  | 	} | ||
|  | 	synerr, ok := err.(*SyntaxError) | ||
|  | 	if !ok { | ||
|  | 		t.Error("Expected SyntaxError.") | ||
|  | 	} | ||
|  | 	if synerr.Line != 3 { | ||
|  | 		t.Error("SyntaxError didn't have correct line number.") | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestTrailingRawToken(t *testing.T) { | ||
|  | 	input := `<FOO></FOO>  ` | ||
|  | 	d := NewDecoder(strings.NewReader(input)) | ||
|  | 	var err error | ||
|  | 	for _, err = d.RawToken(); err == nil; _, err = d.RawToken() { | ||
|  | 	} | ||
|  | 	if err != io.EOF { | ||
|  | 		t.Fatalf("d.RawToken() = _, %v, want _, io.EOF", err) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestTrailingToken(t *testing.T) { | ||
|  | 	input := `<FOO></FOO>  ` | ||
|  | 	d := NewDecoder(strings.NewReader(input)) | ||
|  | 	var err error | ||
|  | 	for _, err = d.Token(); err == nil; _, err = d.Token() { | ||
|  | 	} | ||
|  | 	if err != io.EOF { | ||
|  | 		t.Fatalf("d.Token() = _, %v, want _, io.EOF", err) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestEntityInsideCDATA(t *testing.T) { | ||
|  | 	input := `<test><![CDATA[ &val=foo ]]></test>` | ||
|  | 	d := NewDecoder(strings.NewReader(input)) | ||
|  | 	var err error | ||
|  | 	for _, err = d.Token(); err == nil; _, err = d.Token() { | ||
|  | 	} | ||
|  | 	if err != io.EOF { | ||
|  | 		t.Fatalf("d.Token() = _, %v, want _, io.EOF", err) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | var characterTests = []struct { | ||
|  | 	in  string | ||
|  | 	err string | ||
|  | }{ | ||
|  | 	{"\x12<doc/>", "illegal character code U+0012"}, | ||
|  | 	{"<?xml version=\"1.0\"?>\x0b<doc/>", "illegal character code U+000B"}, | ||
|  | 	{"\xef\xbf\xbe<doc/>", "illegal character code U+FFFE"}, | ||
|  | 	{"<?xml version=\"1.0\"?><doc>\r\n<hiya/>\x07<toots/></doc>", "illegal character code U+0007"}, | ||
|  | 	{"<?xml version=\"1.0\"?><doc \x12='value'>what's up</doc>", "expected attribute name in element"}, | ||
|  | 	{"<doc>&abc\x01;</doc>", "invalid character entity &abc (no semicolon)"}, | ||
|  | 	{"<doc>&\x01;</doc>", "invalid character entity & (no semicolon)"}, | ||
|  | 	{"<doc>&\xef\xbf\xbe;</doc>", "invalid character entity &\uFFFE;"}, | ||
|  | 	{"<doc>&hello;</doc>", "invalid character entity &hello;"}, | ||
|  | } | ||
|  | 
 | ||
|  | func TestDisallowedCharacters(t *testing.T) { | ||
|  | 
 | ||
|  | 	for i, tt := range characterTests { | ||
|  | 		d := NewDecoder(strings.NewReader(tt.in)) | ||
|  | 		var err error | ||
|  | 
 | ||
|  | 		for err == nil { | ||
|  | 			_, err = d.Token() | ||
|  | 		} | ||
|  | 		synerr, ok := err.(*SyntaxError) | ||
|  | 		if !ok { | ||
|  | 			t.Fatalf("input %d d.Token() = _, %v, want _, *SyntaxError", i, err) | ||
|  | 		} | ||
|  | 		if synerr.Msg != tt.err { | ||
|  | 			t.Fatalf("input %d synerr.Msg wrong: want %q, got %q", i, tt.err, synerr.Msg) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | type procInstEncodingTest struct { | ||
|  | 	expect, got string | ||
|  | } | ||
|  | 
 | ||
|  | var procInstTests = []struct { | ||
|  | 	input  string | ||
|  | 	expect [2]string | ||
|  | }{ | ||
|  | 	{`version="1.0" encoding="utf-8"`, [2]string{"1.0", "utf-8"}}, | ||
|  | 	{`version="1.0" encoding='utf-8'`, [2]string{"1.0", "utf-8"}}, | ||
|  | 	{`version="1.0" encoding='utf-8' `, [2]string{"1.0", "utf-8"}}, | ||
|  | 	{`version="1.0" encoding=utf-8`, [2]string{"1.0", ""}}, | ||
|  | 	{`encoding="FOO" `, [2]string{"", "FOO"}}, | ||
|  | } | ||
|  | 
 | ||
|  | func TestProcInstEncoding(t *testing.T) { | ||
|  | 	for _, test := range procInstTests { | ||
|  | 		if got := procInst("version", test.input); got != test.expect[0] { | ||
|  | 			t.Errorf("procInst(version, %q) = %q; want %q", test.input, got, test.expect[0]) | ||
|  | 		} | ||
|  | 		if got := procInst("encoding", test.input); got != test.expect[1] { | ||
|  | 			t.Errorf("procInst(encoding, %q) = %q; want %q", test.input, got, test.expect[1]) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // Ensure that directives with comments include the complete
 | ||
|  | // text of any nested directives.
 | ||
|  | 
 | ||
|  | var directivesWithCommentsInput = ` | ||
|  | <!DOCTYPE [<!-- a comment --><!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">]> | ||
|  | <!DOCTYPE [<!ENTITY go "Golang"><!-- a comment-->]> | ||
|  | <!DOCTYPE <!-> <!> <!----> <!-->--> <!--->--> [<!ENTITY go "Golang"><!-- a comment-->]> | ||
|  | ` | ||
|  | 
 | ||
|  | var directivesWithCommentsTokens = []Token{ | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE [<!ENTITY rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#">]`), | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE [<!ENTITY go "Golang">]`), | ||
|  | 	CharData("\n"), | ||
|  | 	Directive(`DOCTYPE <!-> <!>    [<!ENTITY go "Golang">]`), | ||
|  | 	CharData("\n"), | ||
|  | } | ||
|  | 
 | ||
|  | func TestDirectivesWithComments(t *testing.T) { | ||
|  | 	d := NewDecoder(strings.NewReader(directivesWithCommentsInput)) | ||
|  | 
 | ||
|  | 	for i, want := range directivesWithCommentsTokens { | ||
|  | 		have, err := d.Token() | ||
|  | 		if err != nil { | ||
|  | 			t.Fatalf("token %d: unexpected error: %s", i, err) | ||
|  | 		} | ||
|  | 		if !reflect.DeepEqual(have, want) { | ||
|  | 			t.Errorf("token %d = %#v want %#v", i, have, want) | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // Writer whose Write method always returns an error.
 | ||
|  | type errWriter struct{} | ||
|  | 
 | ||
|  | func (errWriter) Write(p []byte) (n int, err error) { return 0, fmt.Errorf("unwritable") } | ||
|  | 
 | ||
|  | func TestEscapeTextIOErrors(t *testing.T) { | ||
|  | 	expectErr := "unwritable" | ||
|  | 	err := EscapeText(errWriter{}, []byte{'A'}) | ||
|  | 
 | ||
|  | 	if err == nil || err.Error() != expectErr { | ||
|  | 		t.Errorf("have %v, want %v", err, expectErr) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestEscapeTextInvalidChar(t *testing.T) { | ||
|  | 	input := []byte("A \x00 terminated string.") | ||
|  | 	expected := "A \uFFFD terminated string." | ||
|  | 
 | ||
|  | 	buff := new(bytes.Buffer) | ||
|  | 	if err := EscapeText(buff, input); err != nil { | ||
|  | 		t.Fatalf("have %v, want nil", err) | ||
|  | 	} | ||
|  | 	text := buff.String() | ||
|  | 
 | ||
|  | 	if text != expected { | ||
|  | 		t.Errorf("have %v, want %v", text, expected) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | func TestIssue5880(t *testing.T) { | ||
|  | 	type T []byte | ||
|  | 	data, err := Marshal(T{192, 168, 0, 1}) | ||
|  | 	if err != nil { | ||
|  | 		t.Errorf("Marshal error: %v", err) | ||
|  | 	} | ||
|  | 	if !utf8.Valid(data) { | ||
|  | 		t.Errorf("Marshal generated invalid UTF-8: %x", data) | ||
|  | 	} | ||
|  | } |