-
-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathnode_owned_tail_test.go
More file actions
174 lines (146 loc) · 6.63 KB
/
Copy pathnode_owned_tail_test.go
File metadata and controls
174 lines (146 loc) · 6.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package helium_test
import (
"os"
"testing"
"github.com/lestrrat-go/helium"
"github.com/lestrrat-go/helium/enum"
"github.com/stretchr/testify/require"
)
// TestAddChildResolvesAnOwnedTail drives the shapes that safe API can build in
// which a parent's recorded lastChild is not the last node that actually claims
// that parent. AddChild must land the new child after the tail its own child
// list reaches, exactly as AddSibling already does, rather than trusting the
// recorded pointer.
func TestAddChildResolvesAnOwnedTail(t *testing.T) {
t.Parallel()
// A copied external subset claims the document as its parent while living
// only in extSubset, so appending through it records a tail that is on no
// child list. A later AddChild must not link behind that record and lose the
// reachable list.
t.Run("recorded tail is off the child list", func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
dtdPath := dir + "/ext.dtd"
require.NoError(t, os.WriteFile(dtdPath, []byte(`<!ELEMENT root (#PCDATA)>`), 0600))
xml := `<?xml version="1.0"?>
<!DOCTYPE root SYSTEM "` + dtdPath + `">
<root/>`
src, err := helium.NewParser().BlockXXE(false).LoadExternalDTD(true).FS(helium.PermissiveFS()).Parse(t.Context(), []byte(xml))
require.NoError(t, err)
require.NotNil(t, src.ExtSubset())
dst := helium.NewDocument("1.0", "UTF-8", helium.StandaloneImplicitNo)
helium.CopyExtSubset(src, dst)
ext := dst.ExtSubset()
require.NotNil(t, ext)
require.Equal(t, helium.Node(dst), ext.Parent())
// Records dst.lastChild = comment while dst.firstChild stays nil.
comment := dst.CreateComment([]byte("stray"))
require.NoError(t, ext.AddSibling(comment))
root, err := dst.CreateElement("root")
require.NoError(t, err)
require.NoError(t, dst.AddChild(root))
require.Equal(t, helium.Node(root), dst.DocumentElement(),
"the document element must be reachable after the append")
require.Equal(t, helium.Node(root), dst.FirstChild(),
"an empty reachable child list must start at the appended node")
})
// stringToNodeList materializes an entity's replacement children with
// firstChild set and lastChild nil. AddChild must append after the existing
// children instead of discarding them.
t.Run("recorded tail is nil while a child list exists", func(t *testing.T) {
t.Parallel()
doc := helium.NewDocument("1.0", "UTF-8", helium.StandaloneImplicitNo)
dtd, err := doc.CreateInternalSubset("root", "", "")
require.NoError(t, err)
_, err = dtd.AddEntity("e", enum.InternalGeneralEntity, "", "", "val")
require.NoError(t, err)
root, err := doc.CreateElement("root")
require.NoError(t, err)
require.NoError(t, doc.SetDocumentElement(root))
require.NoError(t, root.SetParsedAttribute("a", "&e;"))
ent, ok := doc.GetEntity("e")
require.True(t, ok)
first := ent.FirstChild()
require.NotNil(t, first, "the entity carries its replacement children")
extra, err := doc.CreateElement("extra")
require.NoError(t, err)
require.NoError(t, ent.AddChild(extra))
require.Equal(t, first, ent.FirstChild(),
"the existing child list must survive the append")
var names []string
for child := range helium.Children(ent) {
names = append(names, child.Type().String())
}
require.Contains(t, names, "ElementNode", "the appended child must be reachable")
})
// CreateReference hands the EntityRef the DTD's shared Entity as its child
// while that Entity's parent stays the DTD. Re-adding that same Entity is a
// successful no-op that must preserve both views of the shared node.
t.Run("operand is the parent's own foreign child", func(t *testing.T) {
t.Parallel()
doc := helium.NewDocument("1.0", "UTF-8", helium.StandaloneImplicitNo)
dtd, err := doc.CreateInternalSubset("root", "", "")
require.NoError(t, err)
ent, err := dtd.AddEntity("e1", enum.InternalGeneralEntity, "", "", "x")
require.NoError(t, err)
next, err := dtd.AddEntity("e2", enum.InternalGeneralEntity, "", "", "y")
require.NoError(t, err)
dtdChildren := []helium.Node{ent, next}
require.Equal(t, dtdChildren, collectChildren(dtd))
before, err := helium.WriteString(dtd)
require.NoError(t, err)
ref, err := doc.CreateReference("e1")
require.NoError(t, err)
require.Equal(t, helium.Node(ent), ref.FirstChild())
require.Equal(t, helium.Node(ent), ref.LastChild())
require.NoError(t, ref.AddChild(ent), "re-adding the stored foreign child is a no-op")
require.Equal(t, dtdChildren, collectChildren(dtd),
"the DTD declaration chain must remain intact")
require.Equal(t, helium.Node(dtd), ent.Parent(),
"the shared Entity must remain owned by the DTD")
require.Equal(t, []helium.Node{ent}, collectChildren(ref),
"the reference must retain its shared Entity child")
require.Equal(t, helium.Node(ent), ref.FirstChild())
require.Equal(t, helium.Node(ent), ref.LastChild())
require.Equal(t, helium.Node(next), ent.NextSibling())
require.Equal(t, helium.Node(ent), next.PrevSibling())
after, err := helium.WriteString(dtd)
require.NoError(t, err)
require.Equal(t, before, after, "DTD serialization must retain both declarations")
})
// The same EntityRef shape, with a DIFFERENT node appended. The reference's
// shared Entity child still belongs to the DTD, so its sibling links must not
// be used as an append point for the reference.
t.Run("a foreign first child is not an append anchor", func(t *testing.T) {
t.Parallel()
doc := helium.NewDocument("1.0", "UTF-8", helium.StandaloneImplicitNo)
dtd, err := doc.CreateInternalSubset("root", "", "")
require.NoError(t, err)
ent, err := dtd.AddEntity("e1", enum.InternalGeneralEntity, "", "", "x")
require.NoError(t, err)
next, err := dtd.AddEntity("e2", enum.InternalGeneralEntity, "", "", "y")
require.NoError(t, err)
dtdChildren := []helium.Node{ent, next}
require.Equal(t, dtdChildren, collectChildren(dtd))
ref, err := doc.CreateReference("e1")
require.NoError(t, err)
require.Equal(t, helium.Node(ent), ref.FirstChild())
comment := doc.CreateComment([]byte("added"))
require.NoError(t, ref.AddChild(comment))
require.Equal(t, helium.Node(ref), comment.Parent(),
"the appended node must belong to the requested parent")
require.Equal(t, helium.Node(comment), ref.FirstChild())
require.Equal(t, helium.Node(comment), ref.LastChild())
require.Equal(t, dtdChildren, collectChildren(dtd),
"appending to the reference must not change the DTD declaration chain")
require.Equal(t, helium.Node(next), ent.NextSibling())
require.Equal(t, helium.Node(ent), next.PrevSibling())
})
}
func collectChildren(parent helium.Node) []helium.Node {
var children []helium.Node
for child := range helium.Children(parent) {
children = append(children, child)
}
return children
}