2727import org .junit .jupiter .api .Test ;
2828import org .junit .jupiter .api .io .TempDir ;
2929
30+ import opennlp .tools .wordnet .LexicalKnowledgeBase ;
3031import opennlp .tools .wordnet .Synset ;
31- import opennlp .tools .wordnet .WordNetLexicon ;
3232import opennlp .tools .wordnet .WordNetPos ;
3333import opennlp .tools .wordnet .WordNetRelation ;
3434
4040
4141public class WnLmfReaderTest {
4242
43- static WordNetLexicon fixture () {
43+ static LexicalKnowledgeBase fixture () {
4444 try (InputStream in = WnLmfReaderTest .class .getResourceAsStream ("mini-wn-lmf.xml" )) {
4545 assertNotNull (in , "Fixture mini-wn-lmf.xml must be on the test classpath" );
4646 return WnLmfReader .read (in , "mini-wn-lmf.xml" );
@@ -49,7 +49,7 @@ static WordNetLexicon fixture() {
4949 }
5050 }
5151
52- private static WordNetLexicon parse (String document ) {
52+ private static LexicalKnowledgeBase parse (String document ) {
5353 return WnLmfReader .read (
5454 new ByteArrayInputStream (document .getBytes (StandardCharsets .UTF_8 )), "inline.xml" );
5555 }
@@ -74,7 +74,7 @@ void testLookupReturnsSynsetWithAllComponents() {
7474
7575 @ Test
7676 void testLookupFoldsCaseAndUnderscore () {
77- final WordNetLexicon lexicon = fixture ();
77+ final LexicalKnowledgeBase lexicon = fixture ();
7878 assertEquals ("mini-n1" , lexicon .lookup ("Domestic_Dog" , WordNetPos .NOUN ).get (0 ).id ());
7979 assertEquals ("mini-n1" , lexicon .lookup ("DOG" , WordNetPos .NOUN ).get (0 ).id ());
8080 }
@@ -88,7 +88,7 @@ void testLookupKeepsSenseOrder() {
8888
8989 @ Test
9090 void testLookupIsPosScoped () {
91- final WordNetLexicon lexicon = fixture ();
91+ final LexicalKnowledgeBase lexicon = fixture ();
9292 assertEquals (1 , lexicon .lookup ("run" , WordNetPos .VERB ).size ());
9393 assertTrue (lexicon .lookup ("dog" , WordNetPos .VERB ).isEmpty ());
9494 assertFalse (lexicon .contains ("walk" , WordNetPos .NOUN ));
@@ -97,7 +97,7 @@ void testLookupIsPosScoped() {
9797
9898 @ Test
9999 void testRelationNavigation () {
100- final WordNetLexicon lexicon = fixture ();
100+ final LexicalKnowledgeBase lexicon = fixture ();
101101 assertEquals (List .of ("mini-n1" ), lexicon .related ("mini-n2" , WordNetRelation .HYPONYM ));
102102 assertEquals (List .of ("mini-v1" , "mini-v2" ),
103103 lexicon .related ("mini-v4" , WordNetRelation .HYPONYM ));
@@ -106,7 +106,7 @@ void testRelationNavigation() {
106106
107107 @ Test
108108 void testSenseRelationsAreLiftedToSynsetLevel () {
109- final WordNetLexicon lexicon = fixture ();
109+ final LexicalKnowledgeBase lexicon = fixture ();
110110 assertEquals (List .of ("mini-a2" ), lexicon .related ("mini-a1" , WordNetRelation .ANTONYM ));
111111 assertEquals (List .of ("mini-a1" ), lexicon .related ("mini-a2" , WordNetRelation .ANTONYM ));
112112 assertEquals (List .of ("mini-v1" ),
@@ -126,7 +126,7 @@ void testSatelliteNormalizesToAdjective() {
126126
127127 @ Test
128128 void testUnknownLemmaOrSynsetIsEmpty () {
129- final WordNetLexicon lexicon = fixture ();
129+ final LexicalKnowledgeBase lexicon = fixture ();
130130 assertTrue (lexicon .lookup ("zebra" , WordNetPos .NOUN ).isEmpty ());
131131 assertTrue (lexicon .synset ("mini-n99" ).isEmpty ());
132132 }
@@ -138,7 +138,7 @@ void testReadPath(@TempDir Path tempDir) throws IOException {
138138 "<LexicalEntry id=\" t-cat-n\" ><Lemma writtenForm=\" cat\" partOfSpeech=\" n\" />"
139139 + "<Sense id=\" t-cat-n-1\" synset=\" t-1\" /></LexicalEntry>"
140140 + "<Synset id=\" t-1\" partOfSpeech=\" n\" ><Definition>a feline</Definition></Synset>" ));
141- final WordNetLexicon lexicon = WnLmfReader .read (file );
141+ final LexicalKnowledgeBase lexicon = WnLmfReader .read (file );
142142 assertEquals ("a feline" , lexicon .lookup ("cat" , WordNetPos .NOUN ).get (0 ).gloss ());
143143 }
144144
@@ -157,14 +157,39 @@ void testReadStreamRejectsNulls() {
157157 }
158158
159159 @ Test
160- void testRejectsDoctype () {
160+ void testSkipsDoctypeDeclaration () {
161+ // Real Open English WordNet releases ship exactly this shape: a DOCTYPE naming the schema
162+ // DTD by an unreachable SYSTEM identifier (example.invalid is the RFC 2606 reserved domain
163+ // that must never resolve). The reader must parse past it without attempting to fetch it.
161164 final String document = "<?xml version=\" 1.0\" ?>\n "
162165 + "<!DOCTYPE LexicalResource SYSTEM \" http://example.invalid/WN-LMF-1.3.dtd\" >\n "
163166 + "<LexicalResource><Lexicon id=\" t\" label=\" t\" language=\" en\" version=\" 1\" >"
167+ + "<LexicalEntry id=\" t-cat-n\" ><Lemma writtenForm=\" cat\" partOfSpeech=\" n\" />"
168+ + "<Sense id=\" t-cat-n-1\" synset=\" t-1\" /></LexicalEntry>"
169+ + "<Synset id=\" t-1\" partOfSpeech=\" n\" ><Definition>a feline</Definition></Synset>"
170+ + "</Lexicon></LexicalResource>" ;
171+ final LexicalKnowledgeBase lexicon = parse (document );
172+ assertEquals ("a feline" , lexicon .lookup ("cat" , WordNetPos .NOUN ).get (0 ).gloss ());
173+ }
174+
175+ @ Test
176+ void testInternalSubsetEntityIsNeverExpanded (@ TempDir Path tempDir ) throws IOException {
177+ // A DOCTYPE-declared internal-subset entity is the classic XXE payload: if the parser ever
178+ // honored it, the entity reference below would be replaced by the target file's content.
179+ // With SUPPORT_DTD disabled the declaration itself is never registered, so the reference is
180+ // undefined and parsing must fail loud rather than silently expand it.
181+ final Path secret = tempDir .resolve ("secret.txt" );
182+ Files .writeString (secret , "xxe-marker-should-never-appear" );
183+ final String document = "<?xml version=\" 1.0\" ?>\n "
184+ + "<!DOCTYPE LexicalResource [<!ENTITY xxe SYSTEM \" " + secret .toUri () + "\" >]>\n "
185+ + "<LexicalResource><Lexicon id=\" t\" label=\" t\" language=\" en\" version=\" 1\" >"
186+ + "<LexicalEntry id=\" t-cat-n\" ><Lemma writtenForm=\" &xxe;\" partOfSpeech=\" n\" />"
187+ + "<Sense id=\" t-cat-n-1\" synset=\" t-1\" /></LexicalEntry>"
188+ + "<Synset id=\" t-1\" partOfSpeech=\" n\" ><Definition>a feline</Definition></Synset>"
164189 + "</Lexicon></LexicalResource>" ;
165190 final IllegalArgumentException e =
166191 assertThrows (IllegalArgumentException .class , () -> parse (document ));
167- assertTrue (e .getMessage ().contains ("inline.xml " ));
192+ assertFalse (e .getMessage ().contains ("xxe-marker-should-never-appear " ));
168193 }
169194
170195 @ Test
@@ -212,7 +237,7 @@ void testRejectsUnknownRelationType() {
212237
213238 @ Test
214239 void testSkipsOtherRelationType () {
215- final WordNetLexicon lexicon = parse (
240+ final LexicalKnowledgeBase lexicon = parse (
216241 wrap ("<LexicalEntry id=\" t-cat-n\" ><Lemma writtenForm=\" cat\" partOfSpeech=\" n\" />"
217242 + "<Sense id=\" t-cat-n-1\" synset=\" t-1\" >"
218243 + "<SenseRelation relType=\" other\" target=\" t-cat-n-1\" /></Sense></LexicalEntry>"
0 commit comments