nokogiri 1.19.4-java → 1.19.5-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE-DEPENDENCIES.md +29 -0
  3. data/ext/java/nokogiri/XmlNode.java +69 -38
  4. data/ext/java/nokogiri/XmlReader.java +4 -1
  5. data/ext/java/nokogiri/XmlSchema.java +5 -74
  6. data/ext/java/nokogiri/XmlXpathContext.java +5 -15
  7. data/ext/java/nokogiri/internals/NokogiriEntityResolver.java +5 -7
  8. data/ext/java/nokogiri/internals/NokogiriHelpers.java +68 -0
  9. data/ext/java/nokogiri/internals/NokogiriXPathContext.java +37 -0
  10. data/ext/nokogiri/extconf.rb +1 -0
  11. data/ext/nokogiri/gumbo.c +83 -6
  12. data/ext/nokogiri/html4_document.c +10 -3
  13. data/ext/nokogiri/libxml2_polyfill.c +12 -0
  14. data/ext/nokogiri/nokogiri.c +84 -2
  15. data/ext/nokogiri/xml_document.c +42 -16
  16. data/ext/nokogiri/xml_node.c +73 -38
  17. data/ext/nokogiri/xml_node_set.c +9 -9
  18. data/ext/nokogiri/xml_reader.c +198 -107
  19. data/ext/nokogiri/xml_sax_parser.c +3 -3
  20. data/ext/nokogiri/xml_sax_parser_context.c +38 -7
  21. data/ext/nokogiri/xml_sax_push_parser.c +12 -0
  22. data/ext/nokogiri/xml_xpath_context.c +124 -26
  23. data/ext/nokogiri/xslt_stylesheet.c +123 -27
  24. data/lib/nokogiri/css/parser.rb +35 -18
  25. data/lib/nokogiri/css/parser.y +35 -17
  26. data/lib/nokogiri/css/xpath_visitor.rb +71 -28
  27. data/lib/nokogiri/html5/document.rb +9 -0
  28. data/lib/nokogiri/html5/document_fragment.rb +16 -1
  29. data/lib/nokogiri/html5/node.rb +1 -1
  30. data/lib/nokogiri/html5.rb +16 -0
  31. data/lib/nokogiri/nokogiri.jar +0 -0
  32. data/lib/nokogiri/version/constant.rb +1 -1
  33. data/lib/nokogiri/xml/attr.rb +2 -0
  34. data/lib/nokogiri/xml/attribute_decl.rb +2 -1
  35. data/lib/nokogiri/xml/builder.rb +24 -32
  36. data/lib/nokogiri/xml/document.rb +1 -0
  37. data/lib/nokogiri/xml/dtd.rb +2 -1
  38. data/lib/nokogiri/xml/element_decl.rb +2 -1
  39. data/lib/nokogiri/xml/entity_decl.rb +2 -1
  40. data/lib/nokogiri/xml/node.rb +8 -6
  41. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 32e93c6e2814b80a5552965415a65afff14ae5c0253df5b08fdc0e036dd67a1b
4
- data.tar.gz: 61a91323350c59f22132ca3e67266dc46f9d69bbd7ecec92c192d7a079b84c33
3
+ metadata.gz: e6d7d6a8cdaeda70bba537e8bb1776407fae18003ff7d1b7a1bd406c9704902a
4
+ data.tar.gz: 4de017065a52bc5fe46178fc40313b0a469a81d6508826e4b75909b07832383d
5
5
  SHA512:
6
- metadata.gz: daeef7e9be94229f49fba17511aa0f3be1fefe88a82cac0c94a2ea3b5452e6c937584ec6f1383ac1cbbbcd223c46f91cceb1044e10068b9649ab64fb239d82c6
7
- data.tar.gz: 7f5552b74774881aa06739963f1368b69b3b8e937fc3ce91a89e6c4fd55056acb1bb11d56f5916d35115bc862314eccc7a9cca8a2e4bcf794d88a195fd5de833
6
+ metadata.gz: 8203156eb8d63b7e0edb4ea37a1cc3820b349101931e20d83a08bd2f4734004403f63b1ed6b09366dc70907f11cf5404194e8bb901433dd090321e1db2cf57c4
7
+ data.tar.gz: 2709de3b7f5b823e8b051ee81b2c84e46b4b6465d7801d63797004bbca078e39ba7723b2a2471d32c457a465cdb216b063f4854c00b1ca74ef9f19170040b05c
@@ -15,6 +15,7 @@ Note that this document is broken into multiple sections, each of which describe
15
15
  * [Native WindowsⓇ platform releases ("x64-mingw-ucrt")](#native-windows%E2%93%A1-platform-releases-x64-mingw-ucrt)
16
16
  * [JavaⓇ (JRuby) platform release ("java")](#java%E2%93%A1-jruby-platform-release-java)
17
17
  - [Appendix: Dependencies' License Texts](#appendix-dependencies-license-texts)
18
+ * [hashmap.c](#hashmapc)
18
19
  * [libgumbo](#libgumbo)
19
20
  * [libxml2](#libxml2)
20
21
  * [libxslt](#libxslt)
@@ -114,6 +115,34 @@ This section contains a subsection for each potentially-distributed dependency,
114
115
  Please see previous sections to understand which of these potential dependencies is actually distributed in the gem file you're downloading and using.
115
116
 
116
117
 
118
+ ### hashmap.c
119
+
120
+ MIT
121
+
122
+ https://github.com/tidwall/hashmap.c/blob/master/LICENSE
123
+
124
+ The MIT License (MIT)
125
+
126
+ Copyright (c) 2020 Joshua J Baker
127
+
128
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
129
+ this software and associated documentation files (the "Software"), to deal in
130
+ the Software without restriction, including without limitation the rights to
131
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
132
+ the Software, and to permit persons to whom the Software is furnished to do so,
133
+ subject to the following conditions:
134
+
135
+ The above copyright notice and this permission notice shall be included in all
136
+ copies or substantial portions of the Software.
137
+
138
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
139
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
140
+ FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
141
+ COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
142
+ IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
143
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
144
+
145
+
117
146
  ### libgumbo
118
147
 
119
148
  Apache 2.0
@@ -323,7 +323,7 @@ public class XmlNode extends RubyObject
323
323
  String node_name = rubyStringToString(name);
324
324
  String prefix = NokogiriHelpers.getPrefix(node_name);
325
325
  String namespace_uri = null;
326
- if (document.getDocumentElement() != null) {
326
+ if (prefix != null && document.getDocumentElement() != null) {
327
327
  namespace_uri = document.getDocumentElement().lookupNamespaceURI(prefix);
328
328
  }
329
329
  element = document.createElementNS(namespace_uri, node_name);
@@ -438,25 +438,24 @@ public class XmlNode extends RubyObject
438
438
  if (ns_inherit.isTrue()) {
439
439
  set_namespace(context, ((XmlNode)parent(context)).namespace(context));
440
440
  }
441
- return;
442
- }
443
-
444
- String currentPrefix = e.getParentNode().lookupPrefix(nsURI);
445
- String currentURI = e.getParentNode().lookupNamespaceURI(prefix);
446
- boolean isDefault = e.getParentNode().isDefaultNamespace(nsURI);
447
-
448
- // add xmlns attribute if this is a new root node or if the node's
449
- // namespace isn't a default namespace in the new document
450
- if (e.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
451
- // this is the root node, so we must set the namespaces attributes anyway
452
- e.setAttribute(prefix == null ? "xmlns" : "xmlns:" + prefix, nsURI);
453
- } else if (prefix == null) {
454
- // this is a default namespace but isn't the default where this node is being added
455
- if (!isDefault) { e.setAttribute("xmlns", nsURI); }
456
- } else if (!prefix.equals(currentPrefix) || nsURI.equals(currentURI)) {
457
- // this is a prefixed namespace
458
- // but doesn't have the same prefix or the prefix is set to a different URI
459
- e.setAttribute("xmlns:" + prefix, nsURI);
441
+ } else {
442
+ String currentPrefix = e.getParentNode().lookupPrefix(nsURI);
443
+ String currentURI = e.getParentNode().lookupNamespaceURI(prefix);
444
+ boolean isDefault = e.getParentNode().isDefaultNamespace(nsURI);
445
+
446
+ // add xmlns attribute if this is a new root node or if the node's
447
+ // namespace isn't a default namespace in the new document
448
+ if (e.getParentNode().getNodeType() == Node.DOCUMENT_NODE) {
449
+ // this is the root node, so we must set the namespaces attributes anyway
450
+ e.setAttribute(prefix == null ? "xmlns" : "xmlns:" + prefix, nsURI);
451
+ } else if (prefix == null) {
452
+ // this is a default namespace but isn't the default where this node is being added
453
+ if (!isDefault) { e.setAttribute("xmlns", nsURI); }
454
+ } else if (!prefix.equals(currentPrefix) || !nsURI.equals(currentURI)) {
455
+ // this is a prefixed namespace
456
+ // but doesn't have the same prefix or the prefix is set to a different URI
457
+ e.setAttribute("xmlns:" + prefix, nsURI);
458
+ }
460
459
  }
461
460
 
462
461
  if (e.hasAttributes()) {
@@ -491,6 +490,32 @@ public class XmlNode extends RubyObject
491
490
  }
492
491
  }
493
492
 
493
+ // if this namespace is a duplicate of what's already in the document, remove it.
494
+ // a "duplicate" here is if the prefix and the URI both match what resolves in the parent.
495
+ if (e.getParentNode().getNodeType() == Node.ELEMENT_NODE) {
496
+ RubyArray<?> nsdefs = this.namespace_definitions(context);
497
+ for (int j = 0 ; j < nsdefs.getLength() ; j++) {
498
+ XmlNamespace ns = (XmlNamespace)nsdefs.get(j);
499
+
500
+ String selfPrefix = ns.getPrefix();
501
+ String selfURI = ns.getHref();
502
+ String parentPrefix = e.getParentNode().lookupPrefix(selfURI);
503
+ String parentURI = e.getParentNode().lookupNamespaceURI(selfPrefix);
504
+
505
+ boolean prefixMatch = ((selfPrefix == null && parentPrefix == null) ||
506
+ (selfPrefix != null && selfPrefix.equals(parentPrefix)));
507
+ boolean uriMatch = ((selfURI == null && parentURI == null) ||
508
+ (selfURI != null && selfURI.equals(parentURI)));
509
+
510
+ if (prefixMatch && uriMatch) {
511
+ String attrName = "xmlns";
512
+ if (selfPrefix != null && !selfPrefix.isEmpty()) { attrName = attrName + ':' + selfPrefix; }
513
+
514
+ e.removeAttribute(attrName);
515
+ }
516
+ }
517
+ }
518
+
494
519
  if (this.node.hasChildNodes()) {
495
520
  relink_namespace(context, getChildren());
496
521
  }
@@ -1175,28 +1200,21 @@ public class XmlNode extends RubyObject
1175
1200
  public RubyArray<?>
1176
1201
  namespace_definitions(ThreadContext context)
1177
1202
  {
1178
- // don't use namespace_definitions cache anymore since
1179
- // namespaces might be deleted. Reflecting the result of
1180
- // namespace removals is complicated, so the cache might not be
1181
- // updated.
1203
+ // don't use namespace_definitions cache anymore since namespaces might be deleted. Reflecting
1204
+ // the result of namespace removals is complicated, so the cache might not be updated.
1182
1205
  final XmlDocument doc = document(context.runtime);
1183
1206
  if (doc == null) { return context.runtime.newEmptyArray(); }
1184
1207
  if (doc instanceof Html4Document) { return context.runtime.newEmptyArray(); }
1185
1208
 
1186
- List<XmlNamespace> namespaces = doc.getNamespaceCache().get(node);
1187
- return RubyArray.newArray(context.runtime, namespaces);
1188
-
1189
- // // TODO: I think this implementation would be better but there are edge cases
1190
- // // See https://github.com/sparklemotion/nokogiri/issues/2543
1191
- // RubyArray<?> nsdefs = RubyArray.newArray(context.getRuntime());
1192
- // NamedNodeMap attrs = node.getAttributes();
1193
- // for (int j = 0 ; j < attrs.getLength() ; j++) {
1194
- // Attr attr = (Attr)attrs.item(j);
1195
- // if ("http://www.w3.org/2000/xmlns/" == attr.getNamespaceURI()) {
1196
- // nsdefs.append(XmlNamespace.createFromAttr(context.getRuntime(), attr));
1197
- // }
1198
- // }
1199
- // return nsdefs;
1209
+ RubyArray<?> nsdefs = RubyArray.newArray(context.getRuntime());
1210
+ NamedNodeMap attrs = node.getAttributes();
1211
+ for (int j = 0 ; j < attrs.getLength() ; j++) {
1212
+ Attr attr = (Attr)attrs.item(j);
1213
+ if ("http://www.w3.org/2000/xmlns/" == attr.getNamespaceURI()) {
1214
+ nsdefs.append(XmlNamespace.createFromAttr(context.getRuntime(), attr));
1215
+ }
1216
+ }
1217
+ return nsdefs;
1200
1218
  }
1201
1219
 
1202
1220
  /**
@@ -1506,6 +1524,19 @@ public class XmlNode extends RubyObject
1506
1524
  // and keep the return value. -mbklein
1507
1525
  String new_name = NokogiriHelpers.newQName(ns.getPrefix(), node);
1508
1526
  this.node = NokogiriHelpers.renameNode(node, ns.getHref(), new_name);
1527
+
1528
+ if (node instanceof Element) {
1529
+ Element e = (Element) node;
1530
+ String prefix = ns.getPrefix();
1531
+ String attrName = prefix == null ? "xmlns" : "xmlns:" + prefix;
1532
+ Node parent = e.getParentNode();
1533
+ boolean inScope = "xml".equals(prefix) ||
1534
+ e.hasAttribute(attrName) ||
1535
+ (parent != null && parent.lookupNamespaceURI(prefix) != null);
1536
+ if (!inScope) {
1537
+ e.setAttributeNS("http://www.w3.org/2000/xmlns/", attrName, ns.getHref());
1538
+ }
1539
+ }
1509
1540
  }
1510
1541
 
1511
1542
  clearXpathContext(getNode());
@@ -31,6 +31,7 @@ import org.jruby.RubyBoolean;
31
31
  import org.jruby.RubyClass;
32
32
  import org.jruby.RubyFixnum;
33
33
  import org.jruby.RubyObject;
34
+ import org.jruby.RubyString;
34
35
  import org.jruby.anno.JRubyClass;
35
36
  import org.jruby.anno.JRubyMethod;
36
37
  import org.jruby.exceptions.RaiseException;
@@ -253,7 +254,9 @@ public class XmlReader extends RubyObject
253
254
  // use the default options RECOVER | NONET
254
255
  options = new ParserContext.Options(2048 | 1);
255
256
  }
256
- IRubyObject stringIO = runtime.getClass("StringIO").newInstance(context, args[0], Block.NULL_BLOCK);
257
+ // The parser reads lazily, so hand it a copy the caller cannot change.
258
+ RubyString frozenSource = args[0].convertToString().newFrozen();
259
+ IRubyObject stringIO = runtime.getClass("StringIO").newInstance(context, frozenSource, Block.NULL_BLOCK);
257
260
  InputStream in = new IOInputStream(stringIO);
258
261
  reader.setInput(context, in, url, options);
259
262
  return reader;
@@ -1,6 +1,7 @@
1
1
  package nokogiri;
2
2
 
3
3
  import static nokogiri.internals.NokogiriHelpers.adjustSystemIdIfNecessary;
4
+ import static nokogiri.internals.NokogiriHelpers.effectiveResourceIsLocal;
4
5
  import static nokogiri.internals.NokogiriHelpers.getNokogiriClass;
5
6
 
6
7
  import java.io.IOException;
@@ -8,9 +9,6 @@ import java.io.InputStream;
8
9
  import java.io.Reader;
9
10
  import java.io.StringReader;
10
11
 
11
- import java.net.URI;
12
- import java.net.URISyntaxException;
13
-
14
12
  import javax.xml.XMLConstants;
15
13
  import javax.xml.transform.Source;
16
14
  import javax.xml.transform.dom.DOMSource;
@@ -288,6 +286,10 @@ public class XmlSchema extends RubyObject
288
286
  String systemId,
289
287
  String baseURI)
290
288
  {
289
+ // We enforce NONET for schema resolution by hand because Xerces-J (the JAXP implementation
290
+ // backing XML::Schema on JRuby) does not implement the standard JAXP property
291
+ // XMLConstants.ACCESS_EXTERNAL_SCHEMA — so we cannot simply restrict external access on the
292
+ // SchemaFactory and must classify each resolved resource in the LSResourceResolver instead.
291
293
  if (noNet && !effectiveResourceIsLocal(systemId, baseURI)) {
292
294
  try {
293
295
  this.errorHandler.warning(new SAXParseException(String.format("Attempt to load network entity '%s'", systemId), null));
@@ -304,35 +306,6 @@ public class XmlSchema extends RubyObject
304
306
  }
305
307
  }
306
308
 
307
- // We enforce NONET for schema resolution by hand because Xerces-J (the JAXP implementation
308
- // backing XML::Schema on JRuby) does not implement the standard JAXP property
309
- // XMLConstants.ACCESS_EXTERNAL_SCHEMA — so we cannot simply restrict external access on the
310
- // SchemaFactory and must classify each resolved resource in the LSResourceResolver instead.
311
- //
312
- // Decides whether a schema-import resource may be resolved while NONET is on: true means
313
- // local (allowed), false means a network resource (blocked). A relative systemId inherits
314
- // its document's base, so it is resolved against baseURI before classification — a relative
315
- // import under a remote base is a network fetch even though the systemId alone looks local.
316
- private static boolean
317
- effectiveResourceIsLocal(String systemId, String baseURI)
318
- {
319
- // a null systemId means there is nothing external to resolve
320
- if (systemId == null) {
321
- return true;
322
- }
323
- try {
324
- URI uri = new URI(systemId);
325
- if (baseURI != null && !baseURI.isEmpty()) {
326
- uri = new URI(baseURI).resolve(uri);
327
- }
328
- return isLocalResource(uri);
329
- } catch (URISyntaxException | IllegalArgumentException e) {
330
- // fail closed: an unparseable base or systemId (e.g. a raw UNC path "\\host\share") is
331
- // not provably local, and the JVM's file/URL handling may still reach the network
332
- return false;
333
- }
334
- }
335
-
336
309
  // Test seam for the Ruby suite: local_resource?(systemId, baseURI = nil).
337
310
  @JRubyMethod(meta = true, name = "local_resource?", required = 1, optional = 1, visibility = Visibility.PRIVATE)
338
311
  public static IRubyObject
@@ -343,48 +316,6 @@ public class XmlSchema extends RubyObject
343
316
  return context.runtime.newBoolean(effectiveResourceIsLocal(systemId, baseURI));
344
317
  }
345
318
 
346
- // Classifies an already-parsed URI. Local is a missing scheme, or the "file" scheme, with
347
- // no remote authority and no UNC-shaped path. This is intentionally stricter than libxml2's
348
- // xmlNoNetExternalEntityLoader, which folds a remote host (file://host/...) into a local
349
- // path rather than rejecting it.
350
- //
351
- // TODO: a Windows drive-letter path like "C:\path" parses as scheme "c" and would be
352
- // blocked; support those if we need it later.
353
- private static boolean
354
- isLocalResource(URI uri)
355
- {
356
- // only a missing scheme (a relative or absolute path) or file: can be local; any
357
- // other scheme is a network resource
358
- String scheme = uri.getScheme();
359
- if (scheme != null && !scheme.equalsIgnoreCase("file")) {
360
- return false;
361
- }
362
-
363
- // an opaque "file:" URI (e.g. file:foo, with no "//") is not a usable local path; reject
364
- // it, matching libxml2, which does not resolve that form as a local file either
365
- if (uri.isOpaque()) {
366
- return false;
367
- }
368
-
369
- // a non-empty, non-localhost authority is a remote host — file://host/path, or the
370
- // schemeless network-path form //host/path. Stricter than libxml2, which folds such a
371
- // host into a (failing) local path.
372
- String authority = uri.getRawAuthority();
373
- if (authority != null && !authority.isEmpty() && !authority.equalsIgnoreCase("localhost")) {
374
- return false;
375
- }
376
-
377
- // reject UNC-shaped paths even under an allowed authority: file:////host/share,
378
- // file://localhost//host/share, and %2f/%5c-encoded variants. getPath() is decoded, so
379
- // the encoded forms are normalized before this check.
380
- String path = uri.getPath();
381
- if (path != null && (path.startsWith("//") || path.indexOf('\\') >= 0)) {
382
- return false;
383
- }
384
-
385
- return true;
386
- }
387
-
388
319
  private class SchemaLSInput implements LSInput
389
320
  {
390
321
  protected String fPublicId;
@@ -20,10 +20,10 @@ import org.jruby.anno.JRubyMethod;
20
20
  import org.jruby.exceptions.RaiseException;
21
21
  import org.jruby.runtime.ThreadContext;
22
22
  import org.jruby.runtime.builtin.IRubyObject;
23
- import org.jruby.util.SafePropertyAccessor;
24
23
  import org.w3c.dom.Node;
25
24
 
26
25
  import nokogiri.internals.NokogiriNamespaceContext;
26
+ import nokogiri.internals.NokogiriXPathContext;
27
27
  import nokogiri.internals.NokogiriXPathFunctionResolver;
28
28
  import nokogiri.internals.NokogiriXPathVariableResolver;
29
29
 
@@ -41,16 +41,6 @@ public class XmlXpathContext extends RubyObject
41
41
  {
42
42
  private static final long serialVersionUID = 1L;
43
43
 
44
- static
45
- {
46
- final String DTMManager = "org.apache.xml.dtm.DTMManager";
47
- if (SafePropertyAccessor.getProperty(DTMManager) == null) {
48
- try { // use patched "org.apache.xml.dtm.ref.DTMManagerDefault"
49
- System.setProperty(DTMManager, nokogiri.internals.XalanDTMManagerPatch.class.getName());
50
- } catch (SecurityException ex) { /* no-op - will work although might be slower */ }
51
- }
52
- }
53
-
54
44
  /**
55
45
  * user-data key for (cached) {@link XPathContext}
56
46
  */
@@ -82,7 +72,7 @@ public class XmlXpathContext extends RubyObject
82
72
  rbNew(ThreadContext context, IRubyObject klazz, IRubyObject node)
83
73
  {
84
74
  try {
85
- return new XmlXpathContext(context.runtime, (RubyClass) klazz, (XmlNode) node);
75
+ return new XmlXpathContext(context.runtime, (RubyClass) klazz, XmlNode.asXmlNode(context, node));
86
76
  } catch (IllegalArgumentException e) {
87
77
  throw context.getRuntime().newRuntimeError(e.getMessage());
88
78
  }
@@ -185,7 +175,7 @@ public class XmlXpathContext extends RubyObject
185
175
  public IRubyObject
186
176
  set_node(ThreadContext context, IRubyObject rb_node)
187
177
  {
188
- this.context = (XmlNode) rb_node;
178
+ this.context = XmlNode.asXmlNode(context, rb_node);
189
179
  return rb_node;
190
180
  }
191
181
 
@@ -268,8 +258,8 @@ public class XmlXpathContext extends RubyObject
268
258
  private static XPathContext
269
259
  newXPathContext(final NokogiriXPathFunctionResolver functionResolver)
270
260
  {
271
- if (functionResolver == null) { return new XPathContext(false); }
272
- return new XPathContext(new JAXPExtensionsProvider(functionResolver), false);
261
+ if (functionResolver == null) { return new NokogiriXPathContext(); }
262
+ return new NokogiriXPathContext(new JAXPExtensionsProvider(functionResolver));
273
263
  }
274
264
 
275
265
  private boolean
@@ -1,5 +1,7 @@
1
1
  package nokogiri.internals;
2
2
 
3
+ import static nokogiri.internals.NokogiriHelpers.effectiveResourceIsLocal;
4
+
3
5
  import java.io.ByteArrayInputStream;
4
6
  import java.io.File;
5
7
  import java.io.IOException;
@@ -103,12 +105,12 @@ public class NokogiriEntityResolver implements EntityResolver2
103
105
  } else if (!name.equals("[dtd]") && !options.noEnt) {
104
106
  return emptyInputSource(s);
105
107
  }
106
- String adjustedSystemId;
107
- URI uri = URI.create(systemId);
108
- if (options.noNet && uri.getHost() != null) {
108
+ if (options.noNet && !effectiveResourceIsLocal(systemId, baseURI)) {
109
109
  addError("Attempt to load network entity " + systemId);
110
110
  return emptyInputSource(s);
111
111
  }
112
+ String adjustedSystemId;
113
+ URI uri = URI.create(systemId);
112
114
  // if this is a url or absolute file name then use it
113
115
  if (uri.isAbsolute() && !uri.isOpaque()) {
114
116
  adjustedSystemId = uri.toURL().toString();
@@ -116,10 +118,6 @@ public class NokogiriEntityResolver implements EntityResolver2
116
118
  adjustedSystemId = uri.getPath();
117
119
  } else if (baseURI != null) {
118
120
  URI baseuri = URI.create(baseURI);
119
- if (options.noNet && baseuri.getHost() != null) {
120
- addError("Attempt to load network entity " + systemId);
121
- return emptyInputSource(s);
122
- }
123
121
  if (baseuri.getHost() == null) {
124
122
  // this is a local file
125
123
  adjustedSystemId = join(baseuri.getPath(), uri.getPath()).getCanonicalPath();
@@ -4,6 +4,8 @@ import java.io.ByteArrayInputStream;
4
4
  import java.io.File;
5
5
  import java.lang.reflect.InvocationTargetException;
6
6
  import java.lang.reflect.Method;
7
+ import java.net.URI;
8
+ import java.net.URISyntaxException;
7
9
  import java.nio.ByteBuffer;
8
10
  import java.nio.CharBuffer;
9
11
  import java.nio.charset.Charset;
@@ -674,6 +676,72 @@ public class NokogiriHelpers
674
676
 
675
677
  private static final Set<String> CharsetNames = Charset.availableCharsets().keySet();
676
678
 
679
+ // Decides whether an external resource may be resolved while NONET is on: true means
680
+ // local (allowed), false means a network resource (blocked). A relative systemId inherits
681
+ // its document's base, so it is resolved against baseURI before classification — a relative
682
+ // reference under a remote base is a network fetch even though the systemId alone looks local.
683
+ public static boolean
684
+ effectiveResourceIsLocal(String systemId, String baseURI)
685
+ {
686
+ // a null systemId means there is nothing external to resolve
687
+ if (systemId == null) {
688
+ return true;
689
+ }
690
+ try {
691
+ URI uri = new URI(systemId);
692
+ if (baseURI != null && !baseURI.isEmpty()) {
693
+ uri = new URI(baseURI).resolve(uri);
694
+ }
695
+ return isLocalResource(uri);
696
+ } catch (URISyntaxException | IllegalArgumentException e) {
697
+ // fail closed: an unparseable base or systemId (e.g. a raw UNC path "\\host\share") is
698
+ // not provably local, and the JVM's file/URL handling may still reach the network
699
+ return false;
700
+ }
701
+ }
702
+
703
+ // Classifies an already-parsed URI. Local is a missing scheme, or the "file" scheme, with
704
+ // no remote authority and no UNC-shaped path. This is intentionally stricter than libxml2's
705
+ // xmlNoNetExternalEntityLoader, which folds a remote host (file://host/...) into a local
706
+ // path rather than rejecting it.
707
+ //
708
+ // TODO: a Windows drive-letter path like "C:\path" parses as scheme "c" and would be
709
+ // blocked; support those if we need it later.
710
+ private static boolean
711
+ isLocalResource(URI uri)
712
+ {
713
+ // only a missing scheme (a relative or absolute path) or file: can be local; any
714
+ // other scheme is a network resource
715
+ String scheme = uri.getScheme();
716
+ if (scheme != null && !scheme.equalsIgnoreCase("file")) {
717
+ return false;
718
+ }
719
+
720
+ // an opaque "file:" URI (e.g. file:foo, with no "//") is not a usable local path; reject
721
+ // it, matching libxml2, which does not resolve that form as a local file either
722
+ if (uri.isOpaque()) {
723
+ return false;
724
+ }
725
+
726
+ // a non-empty, non-localhost authority is a remote host — file://host/path, or the
727
+ // schemeless network-path form //host/path. Stricter than libxml2, which folds such a
728
+ // host into a (failing) local path.
729
+ String authority = uri.getRawAuthority();
730
+ if (authority != null && !authority.isEmpty() && !authority.equalsIgnoreCase("localhost")) {
731
+ return false;
732
+ }
733
+
734
+ // reject UNC-shaped paths even under an allowed authority: file:////host/share,
735
+ // file://localhost//host/share, and %2f/%5c-encoded variants. getPath() is decoded, so
736
+ // the encoded forms are normalized before this check.
737
+ String path = uri.getPath();
738
+ if (path != null && (path.startsWith("//") || path.indexOf('\\') >= 0)) {
739
+ return false;
740
+ }
741
+
742
+ return true;
743
+ }
744
+
677
745
  public static String
678
746
  adjustSystemIdIfNecessary(String currentDir, String scriptFileName, String baseURI, String systemId)
679
747
  {
@@ -0,0 +1,37 @@
1
+ package nokogiri.internals;
2
+
3
+ import org.apache.xml.dtm.DTMManager;
4
+ import org.apache.xpath.XPathContext;
5
+ import org.apache.xpath.objects.XMLStringFactoryImpl;
6
+
7
+ /**
8
+ * An XPathContext that uses XalanDTMManagerPatch without looking it up by class name.
9
+ *
10
+ * Xalan's DTMManager.newInstance() looks up the manager class in the thread's context class loader
11
+ * and in the class loader that loaded Xalan. In a servlet container with Xalan in WEB-INF/lib,
12
+ * both are the webapp class loader, which cannot see nokogiri's classes.
13
+ */
14
+ public final class NokogiriXPathContext extends XPathContext
15
+ {
16
+ public
17
+ NokogiriXPathContext()
18
+ {
19
+ super(false);
20
+ m_dtmManager = newDTMManager();
21
+ }
22
+
23
+ public
24
+ NokogiriXPathContext(Object owner)
25
+ {
26
+ super(owner, false);
27
+ m_dtmManager = newDTMManager();
28
+ }
29
+
30
+ private static DTMManager
31
+ newDTMManager()
32
+ {
33
+ DTMManager manager = new XalanDTMManagerPatch();
34
+ manager.setXMLStringFactory(XMLStringFactoryImpl.getFactory());
35
+ return manager;
36
+ }
37
+ }
@@ -1125,6 +1125,7 @@ end
1125
1125
  have_func("xmlCtxtSetOptions") # introduced in libxml2 2.13.0
1126
1126
  have_func("xmlCtxtGetOptions") # introduced in libxml2 2.14.0
1127
1127
  have_func("xmlSwitchEncodingName") # introduced in libxml2 2.13.0
1128
+ have_func("xmlAddIDSafe") # introduced in libxml2 2.13.0
1128
1129
  have_func("rb_category_warning") # introduced in Ruby 3.0 but had trouble resolving this symbol in truffleruby
1129
1130
 
1130
1131
  other_library_versions_string = OTHER_LIBRARY_VERSIONS.map { |k, v| [k, v].join(":") }.join(",")