Repository: mmcdole/gofeed
Pull request #137 (2020-03-03 18:40:57)

Patch godexsoft@ commit ed86b2f106e14101bd44e2f6e4b62d061293ffe6
to new branch as they are out of town and they have a go.mod change that makes merging difficult.

Original cl description:
Fix truncated entity error for ampersand literal

coveralls: 
[![Coverage Status](https://coveralls.io/builds/29126790/badge)](https://coveralls.io/builds/29126790)

Coverage decreased (-0.2%) to 60.637% when pulling **45c43e03b64e1e5cce6d8438e1b9e01369300f56 on hobeone:master** into **1c1bb315fdac37be839792f95a8ee3bbe8cc5af6 on mmcdole:master**.


Patch:
diff --git a/internal/shared/parseutils.go b/internal/shared/parseutils.go
index 475343b..7c79b74 100644
--- a/internal/shared/parseutils.go
+++ b/internal/shared/parseutils.go
@@ -96,60 +96,70 @@ func DecodeEntities(str string) (string, error) {
 			break
 		}
 
-		// Write and skip everything before it
 		buf.Write(data[:idx])
-		data = data[idx+1:]
+		data = data[idx:]
 
-		if len(data) == 0 {
-			return "", TruncatedEntity
+		// If there is only the '&' left here
+		if len(data) == 1 {
+			buf.Write(data)
+			return buf.String(), nil
 		}
 
 		// Find the end of the entity
 		end := bytes.IndexByte(data, ';')
 		if end == -1 {
-			return "", TruncatedEntity
+			// it's not an entitiy. just a plain old '&' possibly with extra bytes
+			buf.Write(data)
+			return buf.String(), nil
 		}
 
-		if data[0] == '#' {
-			// Numerical character reference
-			var str string
-			base := 10
-
-			if len(data) > 1 && data[1] == 'x' {
-				str = string(data[2:end])
-				base = 16
-			} else {
-				str = string(data[1:end])
-			}
-
-			i, err := strconv.ParseUint(str, base, 32)
-			if err != nil {
-				return "", InvalidNumericReference
-			}
-
-			buf.WriteRune(rune(i))
+		// Check if there is a space somewhere within the 'entitiy'.
+		// If there is then skip the whole thing since it's not a real entity.
+		if strings.Contains(string(data[1:end]), " ") {
+			buf.Write(data)
+			return buf.String(), nil
 		} else {
-			// Predefined entity
-			name := string(data[:end])
-
-			var c byte
-			switch name {
-			case "lt":
-				c = '<'
-			case "gt":
-				c = '>'
-			case "quot":
-				c = '"'
-			case "apos":
-				c = '\''
-			case "amp":
-				c = '&'
-			default:
-				return "", fmt.Errorf("unknown predefined "+
-					"entity &%s;", name)
+			if data[1] == '#' {
+				// Numerical character reference
+				var str string
+				base := 10
+
+				if len(data) > 2 && data[2] == 'x' {
+					str = string(data[3:end])
+					base = 16
+				} else {
+					str = string(data[2:end])
+				}
+
+				i, err := strconv.ParseUint(str, base, 32)
+				if err != nil {
+					return "", InvalidNumericReference
+				}
+
+				buf.WriteRune(rune(i))
+			} else {
+				// Predefined entity
+				name := string(data[1:end])
+
+				var c byte
+				switch name {
+				case "lt":
+					c = '<'
+				case "gt":
+					c = '>'
+				case "quot":
+					c = '"'
+				case "apos":
+					c = '\''
+				case "amp":
+					c = '&'
+				default:
+					return "", fmt.Errorf("unknown predefined "+
+						"entity &%s;", name)
+				}
+
+				buf.WriteByte(c)
 			}
-
-			buf.WriteByte(c)
 		}
 
 		// Skip the entity


Test patch:
diff --git a/internal/shared/parseutils_test.go b/internal/shared/parseutils_test.go
index e2af298..c80842d 100644
--- a/internal/shared/parseutils_test.go
+++ b/internal/shared/parseutils_test.go
@@ -13,6 +13,8 @@ func TestDecodeEntities(t *testing.T) {
 	}{
 		{"", ""},
 		{"foo", "foo"},
+		{"skip & normal & amps", "skip & normal & amps"},
+		{"not & entity;hello &ne xt;one", "not & entity;hello &ne xt;one"},
 
 		{"&lt;foo&gt;", "<foo>"},
 		{"a &quot;b&quot; &apos;c&apos;", "a \"b\" 'c'"},
@@ -21,6 +23,10 @@ func TestDecodeEntities(t *testing.T) {
 		{"&#34;foo&#34;", "\"foo\""},
 		{"&#x61;&#x062;&#x0063;", "abc"},
 		{"r&#xe9;sum&#x00E9;", "résumé"},
+		{"&", "&"},
+		{"&foo", "&foo"},
+		{"&lt", "&lt"},
+		{"&#", "&#"},
 	}
 
 	for _, test := range tests {
@@ -35,13 +41,9 @@ func TestDecodeEntities(t *testing.T) {
 func TestDecodeEntitiesInvalid(t *testing.T) {
 	tests := []string{
 		// Predefined entities
-		"&",     // truncated
-		"&foo",  // truncated
 		"&foo;", // unknown
-		"&lt",   // known but truncated
 
 		// Numerical character references
-		"&#",      // truncated
 		"&#;",     // missing number
 		"&#x;",    // missing hexadecimal number
 		"&#12a;",  // invalid decimal number