nokogiri 1.19.4 → 1.19.7

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/nokogiri/extconf.rb +1 -0
  4. data/ext/nokogiri/gumbo.c +83 -6
  5. data/ext/nokogiri/html4_document.c +10 -3
  6. data/ext/nokogiri/libxml2_polyfill.c +12 -0
  7. data/ext/nokogiri/nokogiri.c +84 -2
  8. data/ext/nokogiri/nokogiri.h +19 -1
  9. data/ext/nokogiri/xml_document.c +42 -16
  10. data/ext/nokogiri/xml_node.c +73 -38
  11. data/ext/nokogiri/xml_node_set.c +9 -9
  12. data/ext/nokogiri/xml_reader.c +198 -107
  13. data/ext/nokogiri/xml_sax_parser.c +3 -3
  14. data/ext/nokogiri/xml_sax_parser_context.c +38 -7
  15. data/ext/nokogiri/xml_sax_push_parser.c +12 -0
  16. data/ext/nokogiri/xml_xpath_context.c +124 -26
  17. data/ext/nokogiri/xslt_stylesheet.c +123 -27
  18. data/gumbo-parser/src/Makefile +2 -0
  19. data/gumbo-parser/src/hashmap.c +1154 -0
  20. data/gumbo-parser/src/hashmap.h +62 -0
  21. data/gumbo-parser/src/parser.c +49 -13
  22. data/gumbo-parser/src/string_set.c +41 -0
  23. data/gumbo-parser/src/string_set.h +21 -0
  24. data/gumbo-parser/src/tokenizer.c +49 -19
  25. data/lib/nokogiri/css/parser.rb +35 -18
  26. data/lib/nokogiri/css/parser.y +35 -17
  27. data/lib/nokogiri/css/xpath_visitor.rb +71 -28
  28. data/lib/nokogiri/html5/document.rb +9 -0
  29. data/lib/nokogiri/html5/document_fragment.rb +16 -1
  30. data/lib/nokogiri/html5/node.rb +1 -1
  31. data/lib/nokogiri/html5.rb +16 -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 +5 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b195d6ddd64bd4812056a21283c42ac51a161c99a0763d2cbb0a174e1d03fd12
4
- data.tar.gz: 375787a6a2a62d3d3d394e5bef11e2d1dbb3a1d238af31ffee7e46575d6fe877
3
+ metadata.gz: 9b6ae8d128fac90392dfbe122d0c1e1e8c056fd6dc1c49cfecdef2ac1d09788f
4
+ data.tar.gz: 315ef5552e29320fe8008f9fceff8316718ce263668bf13d1e2f015715203540
5
5
  SHA512:
6
- metadata.gz: 58a9b6de3ff04d37308d889127f0be4e5c08c6780a20084324684be26e9e8544d81b6a4406d3c78d6980dd645c12c9093583d7c7aacb133a9a96ecda6cabe2c0
7
- data.tar.gz: 7f3f742d0510193f0a612ecade4e0115264c6229f09e53a8c750aa7032a5a7d810924ddf5d7c4cd048be14b0c0800e52032ad937536c1f5e8efff4caa0357d15
6
+ metadata.gz: 18e131261027295c823d6e4b4d4d0c739ac8dcfc2a5d570409f3081917f39b4c69f6719f55eb4a9e78c34e390c442abb50267bd9e0cfe8a25bfd3076a4de0bdd
7
+ data.tar.gz: 9ac2042bf9ce20532e61b2b0e29b9582127c034914ddf370bde9a6b0b8abad067c1e4030551451c2f171bb3b48981f83b69aa2c8b45ddb939ffd3fe88d1453ba
@@ -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
@@ -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(",")
data/ext/nokogiri/gumbo.c CHANGED
@@ -113,6 +113,71 @@ set_line(xmlNodePtr node, size_t line)
113
113
  }
114
114
  }
115
115
 
116
+ // This function is essentially xmlNewNsProp, but we skip the full list traversal to append by
117
+ // providing the last property in the linked list as a parameter.
118
+ static xmlAttrPtr
119
+ append_property(xmlNodePtr node, xmlNsPtr ns, const xmlChar *name, const xmlChar *value, xmlAttrPtr last_prop)
120
+ {
121
+ xmlAttrPtr cur = (xmlAttrPtr) xmlMalloc(sizeof(xmlAttr));
122
+ if (cur == NULL) {
123
+ return NULL;
124
+ }
125
+ memset(cur, 0, sizeof(xmlAttr));
126
+ cur->type = XML_ATTRIBUTE_NODE;
127
+ cur->parent = node;
128
+ xmlDocPtr doc = node->doc;
129
+ cur->doc = doc;
130
+ cur->ns = ns;
131
+
132
+ if ((doc != NULL) && (doc->dict != NULL)) {
133
+ cur->name = (xmlChar *) xmlDictLookup(doc->dict, name, -1);
134
+ } else {
135
+ cur->name = xmlStrdup(name);
136
+ }
137
+ if (cur->name == NULL) {
138
+ goto error;
139
+ }
140
+
141
+ if (value != NULL) {
142
+ cur->children = xmlNewDocText(doc, value);
143
+ if (cur->children == NULL) {
144
+ goto error;
145
+ }
146
+ cur->last = NULL;
147
+ xmlNodePtr tmp = cur->children;
148
+ while (tmp != NULL) {
149
+ tmp->parent = (xmlNodePtr) cur;
150
+ if (tmp->next == NULL) {
151
+ cur->last = tmp;
152
+ }
153
+ tmp = tmp->next;
154
+ }
155
+
156
+ if (doc != NULL) {
157
+ int res = xmlIsID(doc, node, cur);
158
+ if (res < 0) {
159
+ goto error;
160
+ }
161
+ if ((res == 1) && (xmlAddIDSafe(cur, value) < 0)) {
162
+ goto error;
163
+ }
164
+ }
165
+ }
166
+
167
+ if (node->properties == NULL) {
168
+ node->properties = cur;
169
+ } else {
170
+ last_prop->next = cur;
171
+ cur->prev = last_prop;
172
+ }
173
+
174
+ return cur;
175
+
176
+ error:
177
+ xmlFreeProp(cur);
178
+ return (NULL);
179
+ }
180
+
116
181
  // Construct an XML tree rooted at xml_output_node from the Gumbo tree rooted
117
182
  // at gumbo_node.
118
183
  static void
@@ -200,6 +265,7 @@ build_tree(
200
265
  xmlAddChild(xml_node, xml_child);
201
266
 
202
267
  // Add the attributes.
268
+ xmlAttrPtr last_prop = NULL;
203
269
  const GumboVector *attrs = &gumbo_child->v.element.attributes;
204
270
  for (size_t i = 0; i < attrs->length; i++) {
205
271
  const GumboAttribute *attr = attrs->data[i];
@@ -220,7 +286,9 @@ build_tree(
220
286
  default:
221
287
  ns = NULL;
222
288
  }
223
- xmlNewNsProp(xml_child, ns, (const xmlChar *)attr->name, (const xmlChar *)attr->value);
289
+
290
+ // We micromanage the attribute list for performance reasons.
291
+ last_prop = append_property(xml_child, ns, (const xmlChar *)attr->name, (const xmlChar *)attr->value, last_prop);
224
292
  }
225
293
 
226
294
  // Add children for this element.
@@ -373,12 +441,20 @@ parse_continue(VALUE parse_args)
373
441
  } else {
374
442
  doc = new_html_doc(NULL, NULL, NULL);
375
443
  }
376
- args->doc = doc; // Make sure doc gets cleaned up if an error is thrown.
444
+
445
+ // We're about to build the libxml2 tree structure from the Gumbo tree
446
+ // structure. If an exception is raised during tree building, control passes
447
+ // to `parse_cleanup()` which needs to free the newly allocated `xmlDoc`,
448
+ // `doc`. If the tree is successfully constructed, then
449
+ // `noko_xml_document_wrap()` will create a new Ruby object which will own
450
+ // `doc`. In that case, `parse_cleanup()` should not free `doc`.
451
+ args->doc = doc;
377
452
  build_tree(doc, (xmlNodePtr)doc, output->document);
453
+ args->doc = NULL;
378
454
  VALUE rdoc = noko_xml_document_wrap(args->klass, doc);
455
+
379
456
  rb_iv_set(rdoc, "@url", args->url_or_frag);
380
457
  rb_iv_set(rdoc, "@quirks_mode", INT2NUM(output->document->v.document.doc_type_quirks_mode));
381
- args->doc = NULL; // The Ruby runtime now owns doc so don't delete it.
382
458
  add_errors(output, rdoc, args->input, args->url_or_frag);
383
459
  return rdoc;
384
460
  }
@@ -512,6 +588,7 @@ error:
512
588
  continue;
513
589
  }
514
590
  VALUE element_name = rb_funcall(node, name, 0);
591
+ Check_Type(element_name, T_STRING);
515
592
  if (RSTRING_LEN(element_name) == 4
516
593
  && !st_strcasecmp(RSTRING_PTR(element_name), "form")
517
594
  && lookup_namespace(node, false) == GUMBO_NAMESPACE_HTML) {
@@ -568,7 +645,7 @@ error:
568
645
  .output = output,
569
646
  .input = tags,
570
647
  .url_or_frag = doc_fragment,
571
- .doc = (xmlDocPtr)extract_xml_node(doc),
648
+ .doc = NULL,
572
649
  };
573
650
  rb_ensure(fragment_continue, (VALUE)(&args), parse_cleanup, (VALUE)(&args));
574
651
  return Qnil;
@@ -580,9 +657,9 @@ fragment_continue(VALUE parse_args)
580
657
  ParseArgs *args = (ParseArgs *)parse_args;
581
658
  GumboOutput *output = args->output;
582
659
  VALUE doc_fragment = args->url_or_frag;
583
- xmlDocPtr xml_doc = args->doc;
660
+ VALUE doc = rb_funcall(doc_fragment, rb_intern_const("document"), 0);
661
+ xmlDocPtr xml_doc = (xmlDocPtr)extract_xml_node(doc);
584
662
 
585
- args->doc = NULL; // The Ruby runtime owns doc so make sure we don't delete it.
586
663
  xmlNodePtr xml_frag = extract_xml_node(doc_fragment);
587
664
  build_tree(xml_doc, xml_frag, output->root);
588
665
  rb_iv_set(doc_fragment, "@quirks_mode", INT2NUM(output->document->v.document.doc_type_quirks_mode));
@@ -18,8 +18,8 @@ rb_html_document_s_new(int argc, VALUE *argv, VALUE klass)
18
18
  htmlDocPtr doc;
19
19
 
20
20
  rb_scan_args(argc, argv, "0*", &rest);
21
- uri = rb_ary_entry(rest, (long)0);
22
- external_id = rb_ary_entry(rest, (long)1);
21
+ uri = rb_ary_entry(rest, (rb_len_t)0);
22
+ external_id = rb_ary_entry(rest, (rb_len_t)1);
23
23
 
24
24
  doc = htmlNewDoc(
25
25
  RTEST(uri) ? (const xmlChar *)StringValueCStr(uri) : NULL,
@@ -48,10 +48,17 @@ rb_html_document_s_read_io(VALUE klass, VALUE rb_io, VALUE rb_url, VALUE rb_enco
48
48
 
49
49
  xmlSetStructuredErrorFunc((void *)rb_error_list, noko__error_array_pusher);
50
50
 
51
- c_doc = htmlReadIO(noko_io_read, noko_io_close, (void *)rb_io, c_url, c_encoding, options);
51
+ noko_io_read_reset_exception();
52
+ c_doc = htmlReadIO(noko_io_read_protected, noko_io_close, (void *)rb_io, c_url, c_encoding, options);
53
+ VALUE rb_io_read_exception = noko_io_read_take_exception();
52
54
 
53
55
  xmlSetStructuredErrorFunc(NULL, NULL);
54
56
 
57
+ if (!NIL_P(rb_io_read_exception)) {
58
+ xmlFreeDoc(c_doc);
59
+ noko_io_read_raise_exception(rb_io_read_exception);
60
+ }
61
+
55
62
  /*
56
63
  * If EncodingFound has occurred in EncodingReader, make sure to do
57
64
  * a cleanup and propagate the error.
@@ -112,3 +112,15 @@ xmlSwitchEncodingName(xmlParserCtxtPtr ctxt, const char *encoding)
112
112
  return (xmlSwitchToEncoding(ctxt, handler));
113
113
  }
114
114
  #endif
115
+
116
+ #ifndef HAVE_XMLADDIDSAFE
117
+ int
118
+ xmlAddIDSafe(xmlAttrPtr attr, const xmlChar *value)
119
+ {
120
+ xmlIDPtr id = xmlAddID(NULL, attr->doc, value, attr);
121
+ if (id) {
122
+ return 1;
123
+ }
124
+ return 0;
125
+ }
126
+ #endif
@@ -50,7 +50,7 @@ void noko_init_html4_sax_parser(void);
50
50
  void noko_init_gumbo(void);
51
51
  void noko_init_test_global_handlers(void);
52
52
 
53
- static ID id_read, id_write, id_external_encoding;
53
+ static ID id_read, id_write, id_external_encoding, id_io_read_exception;
54
54
 
55
55
 
56
56
  static VALUE
@@ -92,6 +92,87 @@ noko_io_read(void *io, char *c_buffer, int c_buffer_len)
92
92
  }
93
93
 
94
94
 
95
+ typedef struct {
96
+ void *io;
97
+ char *c_buffer;
98
+ int c_buffer_len;
99
+ int n_bytes_read;
100
+ } noko_io_read_args_t;
101
+
102
+ static VALUE
103
+ noko_io_read_call(VALUE data)
104
+ {
105
+ noko_io_read_args_t *args = (noko_io_read_args_t *)data;
106
+ args->n_bytes_read = noko_io_read(args->io, args->c_buffer, args->c_buffer_len);
107
+ return Qnil;
108
+ }
109
+
110
+
111
+ /*
112
+ * Like noko_io_read, a StandardError raised by IO#read is swallowed, and libxml2 sees a read
113
+ * error. Any other exception (e.g., Interrupt or Timeout::ExitException) must not unwind through
114
+ * libxml2's stack frames, because that skips libxml2's cleanup and leaks memory, and skips the
115
+ * reset of the error handlers. So it is saved in a fiber-local variable, and $! is cleared until
116
+ * it is re-raised.
117
+ *
118
+ * A caller of libxml2 that passes this callback must call noko_io_read_reset_exception before
119
+ * calling libxml2, and noko_io_read_take_exception after libxml2 returns, and must re-raise a
120
+ * non-nil result with noko_io_read_raise_exception.
121
+ */
122
+ int
123
+ noko_io_read_protected(void *io, char *c_buffer, int c_buffer_len)
124
+ {
125
+ noko_io_read_args_t args = { io, c_buffer, c_buffer_len, -1 };
126
+ int state = 0;
127
+
128
+ rb_protect(noko_io_read_call, (VALUE)&args, &state);
129
+
130
+ if (state) {
131
+ VALUE rb_errinfo_value = rb_errinfo();
132
+ if (RB_TYPE_P(rb_errinfo_value, T_OBJECT) && rb_obj_is_kind_of(rb_errinfo_value, rb_eException)) {
133
+ rb_set_errinfo(Qnil);
134
+ } else {
135
+ rb_errinfo_value = Qnil;
136
+ }
137
+ rb_thread_local_aset(rb_thread_current(), id_io_read_exception,
138
+ rb_assoc_new(INT2FIX(state), rb_errinfo_value));
139
+ return -1;
140
+ }
141
+
142
+ return args.n_bytes_read;
143
+ }
144
+
145
+
146
+ void
147
+ noko_io_read_reset_exception(void)
148
+ {
149
+ rb_thread_local_aset(rb_thread_current(), id_io_read_exception, Qnil);
150
+ }
151
+
152
+
153
+ VALUE
154
+ noko_io_read_take_exception(void)
155
+ {
156
+ VALUE rb_thread = rb_thread_current();
157
+ VALUE rb_exception = rb_thread_local_aref(rb_thread, id_io_read_exception);
158
+
159
+ rb_thread_local_aset(rb_thread, id_io_read_exception, Qnil);
160
+ return rb_exception;
161
+ }
162
+
163
+
164
+ void
165
+ noko_io_read_raise_exception(VALUE rb_exception)
166
+ {
167
+ VALUE rb_errinfo_value = rb_ary_entry(rb_exception, 1);
168
+
169
+ if (!NIL_P(rb_errinfo_value)) {
170
+ rb_set_errinfo(rb_errinfo_value);
171
+ }
172
+ rb_jump_tag(FIX2INT(rb_ary_entry(rb_exception, 0)));
173
+ }
174
+
175
+
95
176
  static VALUE
96
177
  noko_io_write_check(VALUE rb_args)
97
178
  {
@@ -122,7 +203,7 @@ noko_io_write(void *io, char *c_buffer, int c_buffer_len)
122
203
  io_encoding = RB_NIL_P(rb_enc) ? rb_ascii8bit_encoding() : rb_to_encoding(rb_enc);
123
204
 
124
205
  rb_args[0] = rb_io;
125
- rb_args[1] = rb_enc_str_new(c_buffer, (long)c_buffer_len, io_encoding);
206
+ rb_args[1] = rb_enc_str_new(c_buffer, (rb_len_t)c_buffer_len, io_encoding);
126
207
 
127
208
  rb_n_bytes_written = rb_rescue(noko_io_write_check, (VALUE)rb_args, noko_io_write_failed, 0);
128
209
  if (rb_n_bytes_written == Qundef) { return -1; }
@@ -292,6 +373,7 @@ Init_nokogiri(void)
292
373
  noko_init_test_global_handlers();
293
374
 
294
375
  id_read = rb_intern("read");
376
+ id_io_read_exception = rb_intern("__nokogiri_io_read_exception");
295
377
  id_write = rb_intern("write");
296
378
  id_external_encoding = rb_intern("external_encoding");
297
379
  }
@@ -66,6 +66,9 @@ int xmlCtxtGetOptions(xmlParserCtxtPtr ctxt);
66
66
  #ifndef HAVE_XMLSWITCHENCODINGNAME
67
67
  int xmlSwitchEncodingName(xmlParserCtxtPtr ctxt, const char *encoding);
68
68
  #endif
69
+ #ifndef HAVE_XMLADDIDSAFE
70
+ int xmlAddIDSafe(xmlAttrPtr attr, const xmlChar *value);
71
+ #endif
69
72
 
70
73
  #define XMLNS_PREFIX "xmlns"
71
74
  #define XMLNS_PREFIX_LEN 6 /* including either colon or \0 */
@@ -85,7 +88,7 @@ int xmlSwitchEncodingName(xmlParserCtxtPtr ctxt, const char *encoding);
85
88
  #include <ruby/version.h>
86
89
 
87
90
  #define NOKOGIRI_STR_NEW2(str) NOKOGIRI_STR_NEW(str, strlen((const char *)(str)))
88
- #define NOKOGIRI_STR_NEW(str, len) rb_external_str_new_with_enc((const char *)(str), (long)(len), rb_utf8_encoding())
91
+ #define NOKOGIRI_STR_NEW(str, len) rb_external_str_new_with_enc((const char *)(str), (rb_len_t)(len), rb_utf8_encoding())
89
92
  #define RBSTR_OR_QNIL(_str) (_str ? NOKOGIRI_STR_NEW2(_str) : Qnil)
90
93
 
91
94
  #ifndef NORETURN_DECL
@@ -178,6 +181,10 @@ void noko_xml_document_pin_namespace(xmlNsPtr, xmlDocPtr);
178
181
  int noko_xml_document_has_wrapped_blank_nodes_p(xmlDocPtr c_document);
179
182
 
180
183
  int noko_io_read(void *ctx, char *buffer, int len);
184
+ int noko_io_read_protected(void *ctx, char *buffer, int len);
185
+ void noko_io_read_reset_exception(void);
186
+ VALUE noko_io_read_take_exception(void);
187
+ NORETURN_DECL void noko_io_read_raise_exception(VALUE rb_exception);
181
188
  int noko_io_write(void *ctx, char *buffer, int len);
182
189
  int noko_io_close(void *ctx);
183
190
 
@@ -234,6 +241,17 @@ void noko_xml_sax_parser_context_set_encoding(xmlParserCtxtPtr c_context, VALUE
234
241
  # define NOKO_WARN_DEPRECATION(message...) rb_warning(message)
235
242
  #endif
236
243
 
244
+ #ifndef HAVE_RB_LEN_T
245
+ typedef long rb_len_t;
246
+ #endif
247
+ #ifndef RB_NUM2LEN
248
+ #define RB_NUM2LEN RB_NUM2LONG
249
+ #endif
250
+ /* Ruby 4.1.0-preview1 defines HAVE_RB_LEN_T but not RB_FIX2LEN, which came later. */
251
+ #ifndef RB_FIX2LEN
252
+ #define RB_FIX2LEN RB_FIX2LONG
253
+ #endif
254
+
237
255
  void noko__structured_error_func_save(libxmlStructuredErrorHandlerState *handler_state);
238
256
  void noko__structured_error_func_save_and_set(libxmlStructuredErrorHandlerState *handler_state, void *user_data,
239
257
  xmlStructuredErrorFunc handler);
@@ -384,17 +384,24 @@ noko_xml_document_s_read_io(VALUE rb_class,
384
384
 
385
385
  const char *c_url = NIL_P(rb_url) ? NULL : StringValueCStr(rb_url);
386
386
  const char *c_enc = NIL_P(rb_encoding) ? NULL : StringValueCStr(rb_encoding);
387
+ noko_io_read_reset_exception();
387
388
  xmlDocPtr c_document = xmlReadIO(
388
- (xmlInputReadCallback)noko_io_read,
389
+ (xmlInputReadCallback)noko_io_read_protected,
389
390
  (xmlInputCloseCallback)noko_io_close,
390
391
  (void *)rb_io,
391
392
  c_url,
392
393
  c_enc,
393
394
  (int)NUM2INT(rb_options)
394
395
  );
396
+ VALUE rb_io_read_exception = noko_io_read_take_exception();
395
397
 
396
398
  noko__structured_error_func_restore(&handler_state);
397
399
 
400
+ if (!NIL_P(rb_io_read_exception)) {
401
+ xmlFreeDoc(c_document);
402
+ noko_io_read_raise_exception(rb_io_read_exception);
403
+ }
404
+
398
405
  if (c_document == NULL) {
399
406
  xmlFreeDoc(c_document);
400
407
 
@@ -464,7 +471,7 @@ new (int argc, VALUE *argv, VALUE klass)
464
471
  VALUE version, rest, rb_doc ;
465
472
 
466
473
  rb_scan_args(argc, argv, "0*", &rest);
467
- version = rb_ary_entry(rest, (long)0);
474
+ version = rb_ary_entry(rest, (rb_len_t)0);
468
475
  if (NIL_P(version)) { version = rb_str_new2("1.0"); }
469
476
 
470
477
  doc = xmlNewDoc((xmlChar *)StringValueCStr(version));
@@ -609,7 +616,8 @@ rb_xml_document_canonicalize(int argc, VALUE *argv, VALUE self)
609
616
  VALUE rb_namespaces;
610
617
  VALUE rb_comments_p;
611
618
  int c_mode = 0;
612
- xmlChar **c_namespaces;
619
+ xmlChar **c_namespaces = NULL;
620
+ VALUE rb_namespaces_buffer = 0;
613
621
 
614
622
  xmlDocPtr c_doc;
615
623
  xmlOutputBufferPtr c_obuf;
@@ -629,6 +637,35 @@ rb_xml_document_canonicalize(int argc, VALUE *argv, VALUE self)
629
637
  if (c_mode == XML_C14N_1_0 || c_mode == XML_C14N_1_1) {
630
638
  rb_raise(rb_eRuntimeError, "This canonicalizer does not support this operation");
631
639
  }
640
+
641
+ // Copy every prefix into one temporary buffer before libxml2 sees it. Converting first means a
642
+ // raise from #to_str or a null byte leaks nothing. Copying means GC compaction, which moves the
643
+ // bytes of embedded strings, cannot move a prefix while libxml2 reads it. No allocation may
644
+ // happen between reading a prefix's pointer and copying its bytes.
645
+ rb_len_t ns_len = RARRAY_LEN(rb_namespaces);
646
+ VALUE rb_prefixes = rb_ary_new_capa(ns_len);
647
+ for (rb_len_t j = 0; j < ns_len; j++) {
648
+ VALUE entry = rb_ary_entry(rb_namespaces, j);
649
+ StringValueCStr(entry);
650
+ rb_ary_push(rb_prefixes, entry);
651
+ }
652
+
653
+ size_t size = ((size_t)ns_len + 1) * sizeof(xmlChar *);
654
+ for (rb_len_t j = 0; j < ns_len; j++) {
655
+ size += (size_t)RSTRING_LEN(RARRAY_AREF(rb_prefixes, j)) + 1;
656
+ }
657
+ c_namespaces = ALLOCV(rb_namespaces_buffer, size);
658
+
659
+ xmlChar *c_prefix = (xmlChar *)(c_namespaces + ns_len + 1);
660
+ for (rb_len_t j = 0; j < ns_len; j++) {
661
+ VALUE prefix = RARRAY_AREF(rb_prefixes, j);
662
+ size_t prefix_len = (size_t)RSTRING_LEN(prefix);
663
+ memcpy(c_prefix, RSTRING_PTR(prefix), prefix_len);
664
+ c_prefix[prefix_len] = '\0';
665
+ c_namespaces[j] = c_prefix;
666
+ c_prefix += prefix_len + 1;
667
+ }
668
+ c_namespaces[ns_len] = NULL;
632
669
  }
633
670
 
634
671
  c_doc = noko_xml_document_unwrap(self);
@@ -646,24 +683,13 @@ rb_xml_document_canonicalize(int argc, VALUE *argv, VALUE self)
646
683
  rb_callback = (void *)rb_block_proc();
647
684
  }
648
685
 
649
- if (NIL_P(rb_namespaces)) {
650
- c_namespaces = NULL;
651
- } else {
652
- long ns_len = RARRAY_LEN(rb_namespaces);
653
- c_namespaces = ruby_xcalloc((size_t)ns_len + 1, sizeof(xmlChar *));
654
- for (int j = 0 ; j < ns_len ; j++) {
655
- VALUE entry = rb_ary_entry(rb_namespaces, j);
656
- c_namespaces[j] = (xmlChar *)StringValueCStr(entry);
657
- }
658
- }
659
-
660
686
  int ret = xmlC14NExecute(c_doc, c_callback_wrapper, rb_callback,
661
687
  c_mode,
662
688
  c_namespaces,
663
689
  (int)RTEST(rb_comments_p),
664
690
  c_obuf);
665
691
 
666
- ruby_xfree(c_namespaces);
692
+ ALLOCV_END(rb_namespaces_buffer);
667
693
  xmlOutputBufferClose(c_obuf);
668
694
 
669
695
  if (ret < 0) {
@@ -733,7 +759,7 @@ noko_xml_document_has_wrapped_blank_nodes_p(xmlDocPtr c_document)
733
759
  return 0;
734
760
  }
735
761
 
736
- for (long jnode = 0; jnode < RARRAY_LEN(cache); jnode++) {
762
+ for (rb_len_t jnode = 0; jnode < RARRAY_LEN(cache); jnode++) {
737
763
  xmlNodePtr node;
738
764
  VALUE element = rb_ary_entry(cache, jnode);
739
765