ox 2.14.28 → 2.14.29
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +103 -0
- data/ext/ox/base64.c +24 -17
- data/ext/ox/base64.h +2 -1
- data/ext/ox/buf.h +4 -7
- data/ext/ox/builder.c +277 -122
- data/ext/ox/cache.c +48 -13
- data/ext/ox/cache.h +2 -0
- data/ext/ox/dump.c +374 -221
- data/ext/ox/extconf.rb +3 -0
- data/ext/ox/gen_load.c +10 -8
- data/ext/ox/hash_load.c +7 -2
- data/ext/ox/helper.h +10 -6
- data/ext/ox/intern.c +6 -1
- data/ext/ox/obj_load.c +207 -47
- data/ext/ox/ox.c +179 -38
- data/ext/ox/ox.h +11 -1
- data/ext/ox/parse.c +249 -106
- data/ext/ox/sax.c +22 -18
- data/ext/ox/sax_as.c +42 -10
- data/ext/ox/sax_buf.c +23 -8
- data/ext/ox/sax_stack.h +13 -0
- data/ext/ox/time_conv.h +62 -0
- data/ext/ox/xml_check.h +58 -0
- data/ext/ox/xml_str.h +247 -0
- data/lib/ox/version.rb +1 -1
- metadata +5 -2
data/ext/ox/ox.c
CHANGED
|
@@ -11,6 +11,13 @@
|
|
|
11
11
|
#include <stdio.h>
|
|
12
12
|
#include <stdlib.h>
|
|
13
13
|
#include <string.h>
|
|
14
|
+
#include <sys/stat.h>
|
|
15
|
+
#include <sys/types.h>
|
|
16
|
+
|
|
17
|
+
// Not defined by every C library, and it is only these three bits.
|
|
18
|
+
#ifndef S_ISREG
|
|
19
|
+
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
|
20
|
+
#endif
|
|
14
21
|
|
|
15
22
|
#include "intern.h"
|
|
16
23
|
#include "ruby.h"
|
|
@@ -73,6 +80,7 @@ ID ox_start_element_id;
|
|
|
73
80
|
ID ox_string_id;
|
|
74
81
|
ID ox_text_id;
|
|
75
82
|
ID ox_to_c_id;
|
|
83
|
+
ID ox_utc_offset_id;
|
|
76
84
|
ID ox_value_id;
|
|
77
85
|
|
|
78
86
|
VALUE ox_encoding_sym;
|
|
@@ -168,7 +176,7 @@ struct _options ox_default_options = {
|
|
|
168
176
|
SpcSkip, // skip
|
|
169
177
|
No, // smart
|
|
170
178
|
true, // convert_special
|
|
171
|
-
|
|
179
|
+
NotSet, // allow_invalid
|
|
172
180
|
false, // no_empty
|
|
173
181
|
false, // with_cdata
|
|
174
182
|
{'\0'}, // inv_repl
|
|
@@ -282,8 +290,11 @@ static VALUE hints_to_overlay(Hints hints) {
|
|
|
282
290
|
* - _:skip_ [:skip_none|:skip_return|:skip_white|:skip_off] determines how to handle white space in text
|
|
283
291
|
* - _:smart_ [true|false|nil] flag indicating the SAX parser uses hints if available (use with html)
|
|
284
292
|
* - _:convert_special_ [true|false|nil] flag indicating special characters like < are converted with the SAX parser
|
|
285
|
-
* - _:invalid_replace_ [nil|String]
|
|
286
|
-
*
|
|
293
|
+
* - _:invalid_replace_ [nil|false|String] what to do with a character XML can not hold on dump. false, the default,
|
|
294
|
+
* raises an Ox::SyntaxError. nil writes it as a hex character reference, other than a NUL which is dropped since
|
|
295
|
+
* � can not be read back. A string, limited to 10 characters, replaces it and the empty string drops it. This
|
|
296
|
+
* covers text and attribute values. A comment, CDATA section, DOCTYPE or processing instruction expands no character
|
|
297
|
+
* reference, so a character XML can not hold always raises there.
|
|
287
298
|
* - _:no_empty_ [true|false|nil] flag indicating there should be no empty elements in a dump
|
|
288
299
|
* - _:with_cdata_ [true|false] includes cdata in hash_load results
|
|
289
300
|
* - _:strip_namespace_ [String|true|false] false or "" results in no namespace stripping. A string of "*" or true will
|
|
@@ -359,12 +370,14 @@ static VALUE get_def_opts(VALUE self) {
|
|
|
359
370
|
case SpcSkip: rb_hash_aset(opts, skip_sym, skip_white_sym); break;
|
|
360
371
|
default: rb_hash_aset(opts, skip_sym, Qnil); break;
|
|
361
372
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
373
|
+
switch (ox_default_options.allow_invalid) {
|
|
374
|
+
case Yes: rb_hash_aset(opts, invalid_replace_sym, Qnil); break;
|
|
375
|
+
case No:
|
|
365
376
|
rb_hash_aset(opts,
|
|
366
377
|
invalid_replace_sym,
|
|
367
378
|
rb_str_new(ox_default_options.inv_repl + 1, (int)*ox_default_options.inv_repl));
|
|
379
|
+
break;
|
|
380
|
+
default: rb_hash_aset(opts, invalid_replace_sym, Qfalse); break;
|
|
368
381
|
}
|
|
369
382
|
if ('\0' == *ox_default_options.strip_ns) {
|
|
370
383
|
rb_hash_aset(opts, strip_namespace_sym, Qfalse);
|
|
@@ -443,8 +456,11 @@ static VALUE sax_html_overlay(VALUE self) {
|
|
|
443
456
|
* - _:attr_key_mod_ [Proc|nil] converts attribute keys on parse if not nil
|
|
444
457
|
* - _:skip_ [:skip_none|:skip_return|:skip_white|:skip_off] determines how to handle white space in text
|
|
445
458
|
* - _:smart_ [true|false|nil] flag indicating the SAX parser uses hints if available (use with html)
|
|
446
|
-
* - _:invalid_replace_ [nil|String]
|
|
447
|
-
*
|
|
459
|
+
* - _:invalid_replace_ [nil|false|String] what to do with a character XML can not hold on dump. false, the default,
|
|
460
|
+
* raises an Ox::SyntaxError. nil writes it as a hex character reference, other than a NUL which is dropped since
|
|
461
|
+
* � can not be read back. A string, limited to 10 characters, replaces it and the empty string drops it. This
|
|
462
|
+
* covers text and attribute values. A comment, CDATA section, DOCTYPE or processing instruction expands no character
|
|
463
|
+
* reference, so a character XML can not hold always raises there.
|
|
448
464
|
* - _:strip_namespace_ [nil|String|true|false] "" or false result in no namespace stripping. A string of "*" or true
|
|
449
465
|
* will strip all namespaces. Any other non-empty string indicates that matching namespaces will be stripped.
|
|
450
466
|
* - _:with_cdata_ [true|false] includes cdata in hash_load results
|
|
@@ -474,7 +490,10 @@ static VALUE set_def_opts(VALUE self, VALUE opts) {
|
|
|
474
490
|
|
|
475
491
|
v = rb_hash_aref(opts, ox_encoding_sym);
|
|
476
492
|
if (Qnil == v) {
|
|
493
|
+
// Ox.parse_obj and Ox.parse read rb_enc and never re-derive it from the
|
|
494
|
+
// name, so clearing only the name leaves them on the old encoding.
|
|
477
495
|
*ox_default_options.encoding = '\0';
|
|
496
|
+
ox_default_options.rb_enc = 0;
|
|
478
497
|
} else {
|
|
479
498
|
Check_Type(v, T_STRING);
|
|
480
499
|
strncpy(ox_default_options.encoding, StringValuePtr(v), sizeof(ox_default_options.encoding) - 1);
|
|
@@ -483,8 +502,19 @@ static VALUE set_def_opts(VALUE self, VALUE opts) {
|
|
|
483
502
|
|
|
484
503
|
v = rb_hash_aref(opts, ox_indent_sym);
|
|
485
504
|
if (Qnil != v) {
|
|
505
|
+
int indent;
|
|
506
|
+
|
|
486
507
|
Check_Type(v, T_FIXNUM);
|
|
487
|
-
|
|
508
|
+
indent = FIX2INT(v);
|
|
509
|
+
// Bound the indent so that depth * indent (depth up to MAX_DEPTH) cannot
|
|
510
|
+
// overflow the int arithmetic in dump.c and smash the output buffer.
|
|
511
|
+
// Any negative value already means "tight, no newlines"; normalize it.
|
|
512
|
+
if (indent < 0) {
|
|
513
|
+
indent = -1;
|
|
514
|
+
} else if (OX_MAX_INDENT < indent) {
|
|
515
|
+
rb_raise(ox_parse_error_class, ":indent can be no larger than %d.\n", OX_MAX_INDENT);
|
|
516
|
+
}
|
|
517
|
+
ox_default_options.indent = indent;
|
|
488
518
|
}
|
|
489
519
|
|
|
490
520
|
v = rb_hash_aref(opts, trace_sym);
|
|
@@ -560,9 +590,19 @@ static VALUE set_def_opts(VALUE self, VALUE opts) {
|
|
|
560
590
|
rb_raise(ox_parse_error_class, ":no_empty must be true or false.\n");
|
|
561
591
|
}
|
|
562
592
|
|
|
563
|
-
v =
|
|
593
|
+
v = rb_hash_lookup(opts, invalid_replace_sym);
|
|
564
594
|
if (Qnil == v) {
|
|
565
|
-
|
|
595
|
+
// A key that is not there asks for the default, an explicit nil asks for
|
|
596
|
+
// the character to be written out, so the two cannot share a branch.
|
|
597
|
+
if (Qtrue == rb_funcall(opts, has_key_id, 1, invalid_replace_sym)) {
|
|
598
|
+
ox_default_options.allow_invalid = Yes;
|
|
599
|
+
} else {
|
|
600
|
+
ox_default_options.allow_invalid = NotSet;
|
|
601
|
+
*ox_default_options.inv_repl = '\0';
|
|
602
|
+
}
|
|
603
|
+
} else if (Qfalse == v) {
|
|
604
|
+
ox_default_options.allow_invalid = NotSet;
|
|
605
|
+
*ox_default_options.inv_repl = '\0';
|
|
566
606
|
} else {
|
|
567
607
|
long slen;
|
|
568
608
|
|
|
@@ -665,31 +705,63 @@ static VALUE set_def_opts(VALUE self, VALUE opts) {
|
|
|
665
705
|
* - +xml+ [String] XML String in optimized Object format.
|
|
666
706
|
* *return* [Object] deserialized Object.
|
|
667
707
|
*/
|
|
708
|
+
// Arguments for an object-mode parse that runs with GC disabled.
|
|
709
|
+
struct _objParse {
|
|
710
|
+
char *xml;
|
|
711
|
+
size_t len;
|
|
712
|
+
Options options;
|
|
713
|
+
Err err;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
static VALUE obj_parse_body(VALUE argsv) {
|
|
717
|
+
struct _objParse *args = (struct _objParse *)argsv;
|
|
718
|
+
|
|
719
|
+
return ox_parse(args->xml, args->len, ox_obj_callbacks, 0, args->options, args->err);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// rb_gc_disable() returns Qtrue when GC was already disabled. Only re-enable
|
|
723
|
+
// when this call is the one that disabled it. Runs even if the parse raised.
|
|
724
|
+
static VALUE obj_parse_reenable(VALUE was_disabled) {
|
|
725
|
+
if (Qtrue != was_disabled) {
|
|
726
|
+
rb_gc_enable();
|
|
727
|
+
}
|
|
728
|
+
return Qnil;
|
|
729
|
+
}
|
|
730
|
+
|
|
668
731
|
static VALUE to_obj(VALUE self, VALUE ruby_xml) {
|
|
669
|
-
char
|
|
670
|
-
size_t
|
|
671
|
-
VALUE
|
|
672
|
-
|
|
673
|
-
struct
|
|
732
|
+
char *xml, *x, *str;
|
|
733
|
+
size_t len;
|
|
734
|
+
VALUE obj;
|
|
735
|
+
VALUE was_disabled;
|
|
736
|
+
struct _options options = ox_default_options;
|
|
737
|
+
struct _err err;
|
|
738
|
+
struct _objParse args;
|
|
674
739
|
|
|
675
740
|
err_init(&err);
|
|
676
741
|
Check_Type(ruby_xml, T_STRING);
|
|
677
742
|
/* the xml string gets modified so make a copy of it */
|
|
743
|
+
str = StringValuePtr(ruby_xml);
|
|
678
744
|
len = RSTRING_LEN(ruby_xml) + 1;
|
|
679
|
-
x = defuse_bom(
|
|
745
|
+
x = defuse_bom(str, &options);
|
|
746
|
+
// Drop the BOM from len as well, otherwise the copy below reads past the
|
|
747
|
+
// end of the Ruby string and xml is left without a terminating '\0'.
|
|
748
|
+
len -= (size_t)(x - str);
|
|
680
749
|
if (SMALL_XML < len) {
|
|
681
750
|
xml = ALLOC_N(char, len);
|
|
682
751
|
} else {
|
|
683
752
|
xml = ALLOCA_N(char, len);
|
|
684
753
|
}
|
|
685
754
|
memcpy(xml, x, len);
|
|
686
|
-
|
|
687
|
-
|
|
755
|
+
args.xml = xml;
|
|
756
|
+
args.len = len - 1;
|
|
757
|
+
args.options = &options;
|
|
758
|
+
args.err = &err;
|
|
759
|
+
was_disabled = rb_gc_disable();
|
|
760
|
+
obj = rb_ensure(obj_parse_body, (VALUE)&args, obj_parse_reenable, was_disabled);
|
|
688
761
|
if (SMALL_XML < len) {
|
|
689
762
|
xfree(xml);
|
|
690
763
|
}
|
|
691
764
|
RB_GC_GUARD(obj);
|
|
692
|
-
rb_gc_enable();
|
|
693
765
|
if (err_has(&err)) {
|
|
694
766
|
ox_err_raise(&err);
|
|
695
767
|
}
|
|
@@ -705,7 +777,7 @@ static VALUE to_obj(VALUE self, VALUE ruby_xml) {
|
|
|
705
777
|
* _raise_ [Exception] if the XML is malformed.
|
|
706
778
|
*/
|
|
707
779
|
static VALUE to_gen(VALUE self, VALUE ruby_xml) {
|
|
708
|
-
char *xml, *x;
|
|
780
|
+
char *xml, *x, *str;
|
|
709
781
|
size_t len;
|
|
710
782
|
VALUE obj;
|
|
711
783
|
struct _options options = ox_default_options;
|
|
@@ -714,8 +786,12 @@ static VALUE to_gen(VALUE self, VALUE ruby_xml) {
|
|
|
714
786
|
err_init(&err);
|
|
715
787
|
Check_Type(ruby_xml, T_STRING);
|
|
716
788
|
/* the xml string gets modified so make a copy of it */
|
|
789
|
+
str = StringValuePtr(ruby_xml);
|
|
717
790
|
len = RSTRING_LEN(ruby_xml) + 1;
|
|
718
|
-
x = defuse_bom(
|
|
791
|
+
x = defuse_bom(str, &options);
|
|
792
|
+
// Drop the BOM from len as well, otherwise the copy below reads past the
|
|
793
|
+
// end of the Ruby string and xml is left without a terminating '\0'.
|
|
794
|
+
len -= (size_t)(x - str);
|
|
719
795
|
if (SMALL_XML < len) {
|
|
720
796
|
xml = ALLOC_N(char, len);
|
|
721
797
|
} else {
|
|
@@ -789,6 +865,9 @@ static int load_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
789
865
|
} else if (invalid_replace_sym == k) {
|
|
790
866
|
if (Qnil == v) {
|
|
791
867
|
copts->allow_invalid = Yes;
|
|
868
|
+
} else if (Qfalse == v) {
|
|
869
|
+
copts->allow_invalid = NotSet;
|
|
870
|
+
*copts->inv_repl = '\0';
|
|
792
871
|
} else {
|
|
793
872
|
long slen;
|
|
794
873
|
|
|
@@ -845,6 +924,7 @@ static int load_options_cb(VALUE k, VALUE v, VALUE opts) {
|
|
|
845
924
|
|
|
846
925
|
static VALUE load(char *xml, size_t len, int argc, VALUE *argv, VALUE self, VALUE encoding, Err err) {
|
|
847
926
|
VALUE obj;
|
|
927
|
+
char *body;
|
|
848
928
|
struct _options options = ox_default_options;
|
|
849
929
|
|
|
850
930
|
if (1 == argc && rb_cHash == rb_obj_class(*argv)) {
|
|
@@ -859,14 +939,21 @@ static VALUE load(char *xml, size_t len, int argc, VALUE *argv, VALUE self, VALU
|
|
|
859
939
|
} else if (0 == options.rb_enc) {
|
|
860
940
|
options.rb_enc = rb_enc_find(options.encoding);
|
|
861
941
|
}
|
|
862
|
-
|
|
942
|
+
// Shorten len by the size of the BOM so that xml + len stays the address
|
|
943
|
+
// of the terminating '\0' rather than running past the end of the buffer.
|
|
944
|
+
body = defuse_bom(xml, &options);
|
|
945
|
+
len -= (size_t)(body - xml);
|
|
946
|
+
xml = body;
|
|
863
947
|
switch (options.mode) {
|
|
864
|
-
case ObjMode:
|
|
865
|
-
|
|
866
|
-
|
|
948
|
+
case ObjMode: {
|
|
949
|
+
struct _objParse args = {xml, len, &options, err};
|
|
950
|
+
VALUE was_disabled;
|
|
951
|
+
|
|
952
|
+
was_disabled = rb_gc_disable();
|
|
953
|
+
obj = rb_ensure(obj_parse_body, (VALUE)&args, obj_parse_reenable, was_disabled);
|
|
867
954
|
RB_GC_GUARD(obj);
|
|
868
|
-
rb_gc_enable();
|
|
869
955
|
break;
|
|
956
|
+
}
|
|
870
957
|
case GenMode: obj = ox_parse(xml, len, ox_gen_callbacks, 0, &options, err); break;
|
|
871
958
|
case LimMode: obj = ox_parse(xml, len, ox_limited_callbacks, 0, &options, err); break;
|
|
872
959
|
case HashMode:
|
|
@@ -905,14 +992,24 @@ static VALUE load(char *xml, size_t len, int argc, VALUE *argv, VALUE self, VALU
|
|
|
905
992
|
* - _:limited_ - read as a generic XML file but with callbacks on text and elements events only
|
|
906
993
|
* - _:hash_ - read and convert to a Hash and core class objects only
|
|
907
994
|
* - _:hash_no_attrs_ - read and convert to a Hash and core class objects only without capturing attributes
|
|
995
|
+
*
|
|
996
|
+
* If neither this option nor Ox.default_options[:mode] is set the document
|
|
997
|
+
* may pick the mode itself with an <?ox mode="generic"?> or an
|
|
998
|
+
* <?ox mode="limited"?> processing instruction. A document can never pick
|
|
999
|
+
* object mode. Only the caller can, with mode: :object, since object mode
|
|
1000
|
+
* allocates the classes the document names and sets their instance
|
|
1001
|
+
* variables without calling initialize.
|
|
908
1002
|
* - *:effort* [:strict|:tolerant|:auto_define] effort to use when an undefined class is encountered, default: :strict
|
|
909
1003
|
* - _:strict_ - raise an NameError for missing classes and modules
|
|
910
1004
|
* - _:tolerant_ - return nil for missing classes and modules
|
|
911
1005
|
* - _:auto_define_ - auto define missing classes and modules
|
|
912
1006
|
* - *:trace* [Fixnum] trace level as a Fixnum, default: 0 (silent)
|
|
913
1007
|
* - *:symbolize_keys* [true|false|nil] symbolize element attribute keys or leave as Strings
|
|
914
|
-
* - *:invalid_replace* [nil|String]
|
|
915
|
-
*
|
|
1008
|
+
* - *:invalid_replace* [nil|false|String] what to do with a character XML can not hold on dump. false, the default,
|
|
1009
|
+
* raises an Ox::SyntaxError. nil writes it as a hex character reference, other than a NUL which is dropped since
|
|
1010
|
+
* � can not be read back. A string, limited to 10 characters, replaces it and the empty string drops it. This
|
|
1011
|
+
* covers text and attribute values. A comment, CDATA section, DOCTYPE or processing instruction expands no character
|
|
1012
|
+
* reference, so a character XML can not hold always raises there.
|
|
916
1013
|
* - *:strip_namespace* [String|true|false] "" or false result in no namespace stripping. A string of "*" or true will
|
|
917
1014
|
* strip all namespaces. Any other non-empty string indicates that matching namespaces will be stripped.
|
|
918
1015
|
* - *:with_cdata* [true|false] if true cdata is included in hash_load output otherwise it is not.
|
|
@@ -924,6 +1021,9 @@ static VALUE load_str(int argc, VALUE *argv, VALUE self) {
|
|
|
924
1021
|
VALUE encoding;
|
|
925
1022
|
struct _err err;
|
|
926
1023
|
|
|
1024
|
+
if (1 > argc) {
|
|
1025
|
+
rb_raise(ox_arg_error_class, "missing XML string");
|
|
1026
|
+
}
|
|
927
1027
|
err_init(&err);
|
|
928
1028
|
Check_Type(*argv, T_STRING);
|
|
929
1029
|
/* the xml string gets modified so make a copy of it */
|
|
@@ -959,14 +1059,24 @@ static VALUE load_str(int argc, VALUE *argv, VALUE self) {
|
|
|
959
1059
|
* - _:limited_ - read as a generic XML file but with callbacks on text and elements events only
|
|
960
1060
|
* - _:hash_ - read and convert to a Hash and core class objects only
|
|
961
1061
|
* - _:hash_no_attrs_ - read and convert to a Hash and core class objects only without capturing attributes
|
|
1062
|
+
*
|
|
1063
|
+
* If neither this option nor Ox.default_options[:mode] is set the document
|
|
1064
|
+
* may pick the mode itself with an <?ox mode="generic"?> or an
|
|
1065
|
+
* <?ox mode="limited"?> processing instruction. A document can never pick
|
|
1066
|
+
* object mode. Only the caller can, with mode: :object, since object mode
|
|
1067
|
+
* allocates the classes the document names and sets their instance
|
|
1068
|
+
* variables without calling initialize.
|
|
962
1069
|
* - *:effort* [:strict|:tolerant|:auto_define] effort to use when an undefined class is encountered, default: :strict
|
|
963
1070
|
* - _:strict_ - raise an NameError for missing classes and modules
|
|
964
1071
|
* - _:tolerant_ - return nil for missing classes and modules
|
|
965
1072
|
* - _:auto_define_ - auto define missing classes and modules
|
|
966
1073
|
* - *:trace* [Fixnum] trace level as a Fixnum, default: 0 (silent)
|
|
967
1074
|
* - *:symbolize_keys* [true|false|nil] symbolize element attribute keys or leave as Strings
|
|
968
|
-
* - *:invalid_replace* [nil|String]
|
|
969
|
-
*
|
|
1075
|
+
* - *:invalid_replace* [nil|false|String] what to do with a character XML can not hold on dump. false, the default,
|
|
1076
|
+
* raises an Ox::SyntaxError. nil writes it as a hex character reference, other than a NUL which is dropped since
|
|
1077
|
+
* � can not be read back. A string, limited to 10 characters, replaces it and the empty string drops it. This
|
|
1078
|
+
* covers text and attribute values. A comment, CDATA section, DOCTYPE or processing instruction expands no character
|
|
1079
|
+
* reference, so a character XML can not hold always raises there.
|
|
970
1080
|
* - *:strip_namespace* [String|true|false] "" or false result in no namespace stripping. A string of "*" or true will
|
|
971
1081
|
* strip all namespaces. Any other non-empty string indicates that matching namespaces will be stripped.
|
|
972
1082
|
*/
|
|
@@ -975,23 +1085,34 @@ static VALUE load_file(int argc, VALUE *argv, VALUE self) {
|
|
|
975
1085
|
char *xml;
|
|
976
1086
|
FILE *f;
|
|
977
1087
|
off_t len;
|
|
1088
|
+
struct stat st;
|
|
978
1089
|
VALUE obj;
|
|
979
1090
|
struct _err err;
|
|
980
1091
|
|
|
1092
|
+
if (1 > argc) {
|
|
1093
|
+
rb_raise(ox_arg_error_class, "missing file path");
|
|
1094
|
+
}
|
|
981
1095
|
err_init(&err);
|
|
982
1096
|
Check_Type(*argv, T_STRING);
|
|
983
1097
|
path = StringValuePtr(*argv);
|
|
984
|
-
if (0 == (f = fopen(path, "
|
|
1098
|
+
if (0 == (f = fopen(path, "rb"))) {
|
|
985
1099
|
rb_raise(rb_eIOError, "%s\n", strerror(errno));
|
|
986
1100
|
}
|
|
987
|
-
|
|
988
|
-
|
|
1101
|
+
// A stream that can not seek leaves len at -1, which allocates nothing and
|
|
1102
|
+
// then asks fread for SIZE_MAX bytes.
|
|
1103
|
+
// Only a regular file has a size worth sizing a read from. A FIFO reports
|
|
1104
|
+
// -1, and a directory can report anything at all, including off_t's
|
|
1105
|
+
// maximum.
|
|
1106
|
+
if (0 != fstat(fileno(f), &st) || !S_ISREG(st.st_mode)) {
|
|
1107
|
+
fclose(f);
|
|
1108
|
+
rb_raise(rb_eIOError, "%s is not a regular file.\n", path);
|
|
1109
|
+
}
|
|
1110
|
+
len = st.st_size;
|
|
989
1111
|
if (SMALL_XML < len) {
|
|
990
|
-
xml = ALLOC_N(char, len + 1);
|
|
1112
|
+
xml = ALLOC_N(char, (size_t)len + 1);
|
|
991
1113
|
} else {
|
|
992
|
-
xml = ALLOCA_N(char, len + 1);
|
|
1114
|
+
xml = ALLOCA_N(char, (size_t)len + 1);
|
|
993
1115
|
}
|
|
994
|
-
fseek(f, 0, SEEK_SET);
|
|
995
1116
|
if ((size_t)len != fread(xml, 1, len, f)) {
|
|
996
1117
|
ox_err_set(&err, rb_eLoadError, "Failed to read %ld bytes from %s.\n", (long)len, path);
|
|
997
1118
|
obj = Qnil;
|
|
@@ -1178,10 +1299,20 @@ static void parse_dump_options(VALUE ropts, Options copts) {
|
|
|
1178
1299
|
VALUE v;
|
|
1179
1300
|
|
|
1180
1301
|
if (Qnil != (v = rb_hash_lookup(ropts, ox_indent_sym))) {
|
|
1302
|
+
int indent;
|
|
1303
|
+
|
|
1181
1304
|
if (rb_cInteger != rb_obj_class(v) && T_FIXNUM != rb_type(v)) {
|
|
1182
1305
|
rb_raise(ox_parse_error_class, ":indent must be a Fixnum.\n");
|
|
1183
1306
|
}
|
|
1184
|
-
|
|
1307
|
+
indent = NUM2INT(v);
|
|
1308
|
+
// See load_default_options(): bound indent to avoid int overflow in
|
|
1309
|
+
// the dump.c indent arithmetic (oj CVE-2026-54502 class of bug).
|
|
1310
|
+
if (indent < 0) {
|
|
1311
|
+
indent = -1;
|
|
1312
|
+
} else if (OX_MAX_INDENT < indent) {
|
|
1313
|
+
rb_raise(ox_parse_error_class, ":indent can be no larger than %d.\n", OX_MAX_INDENT);
|
|
1314
|
+
}
|
|
1315
|
+
copts->indent = indent;
|
|
1185
1316
|
}
|
|
1186
1317
|
if (Qnil != (v = rb_hash_lookup(ropts, trace_sym))) {
|
|
1187
1318
|
if (rb_cInteger != rb_obj_class(v) && T_FIXNUM != rb_type(v)) {
|
|
@@ -1214,6 +1345,9 @@ static void parse_dump_options(VALUE ropts, Options copts) {
|
|
|
1214
1345
|
if (Qtrue == rb_funcall(ropts, has_key_id, 1, invalid_replace_sym)) {
|
|
1215
1346
|
copts->allow_invalid = Yes;
|
|
1216
1347
|
}
|
|
1348
|
+
} else if (Qfalse == v) {
|
|
1349
|
+
copts->allow_invalid = NotSet;
|
|
1350
|
+
*copts->inv_repl = '\0';
|
|
1217
1351
|
} else {
|
|
1218
1352
|
long slen;
|
|
1219
1353
|
|
|
@@ -1286,6 +1420,9 @@ static VALUE dump(int argc, VALUE *argv, VALUE self) {
|
|
|
1286
1420
|
struct _options copts = ox_default_options;
|
|
1287
1421
|
VALUE rstr;
|
|
1288
1422
|
|
|
1423
|
+
if (1 > argc) {
|
|
1424
|
+
rb_raise(ox_arg_error_class, "missing object to dump");
|
|
1425
|
+
}
|
|
1289
1426
|
if (2 == argc) {
|
|
1290
1427
|
parse_dump_options(argv[1], &copts);
|
|
1291
1428
|
}
|
|
@@ -1348,6 +1485,9 @@ static VALUE to_xml(int argc, VALUE *argv, VALUE self) {
|
|
|
1348
1485
|
static VALUE to_file(int argc, VALUE *argv, VALUE self) {
|
|
1349
1486
|
struct _options copts = ox_default_options;
|
|
1350
1487
|
|
|
1488
|
+
if (2 > argc) {
|
|
1489
|
+
rb_raise(ox_arg_error_class, "missing file path or object to write");
|
|
1490
|
+
}
|
|
1351
1491
|
if (3 == argc) {
|
|
1352
1492
|
parse_dump_options(argv[2], &copts);
|
|
1353
1493
|
}
|
|
@@ -1447,6 +1587,7 @@ void Init_ox(void) {
|
|
|
1447
1587
|
ox_string_id = rb_intern("string");
|
|
1448
1588
|
ox_text_id = rb_intern("text");
|
|
1449
1589
|
ox_to_c_id = rb_intern("to_c");
|
|
1590
|
+
ox_utc_offset_id = rb_intern("utc_offset");
|
|
1450
1591
|
ox_value_id = rb_intern("value");
|
|
1451
1592
|
|
|
1452
1593
|
encoding_id = rb_intern("encoding");
|
data/ext/ox/ox.h
CHANGED
|
@@ -34,6 +34,14 @@ extern "C" {
|
|
|
34
34
|
#define raise_error(msg, xml, current) _ox_raise_error(msg, xml, current, __FILE__, __LINE__)
|
|
35
35
|
|
|
36
36
|
#define MAX_TEXT_LEN 4096
|
|
37
|
+
// Anything even close to the max prolog length is most likely someone trying
|
|
38
|
+
// to break something.
|
|
39
|
+
#define MAX_PROLOG 32767
|
|
40
|
+
|
|
41
|
+
/* Upper bound for the :indent dump option. Keeps depth * indent (depth is
|
|
42
|
+
* capped at the dump MAX_DEPTH of 1000) far below INT_MAX so the indent size
|
|
43
|
+
* arithmetic in dump.c cannot overflow and under-grow the output buffer. */
|
|
44
|
+
#define OX_MAX_INDENT 16384
|
|
37
45
|
|
|
38
46
|
#define SILENT 0
|
|
39
47
|
#define TRACE 1
|
|
@@ -90,7 +98,7 @@ typedef struct _parseCallbacks {
|
|
|
90
98
|
void (*add_doctype)(PInfo pi, const char *docType);
|
|
91
99
|
void (*add_comment)(PInfo pi, const char *comment);
|
|
92
100
|
void (*add_cdata)(PInfo pi, const char *cdata, size_t len);
|
|
93
|
-
void (*add_text)(PInfo pi, char *text, int closed);
|
|
101
|
+
void (*add_text)(PInfo pi, char *text, size_t len, int closed);
|
|
94
102
|
void (*add_element)(PInfo pi, const char *ename, Attr attrs, int hasChildren);
|
|
95
103
|
void (*end_element)(PInfo pi, const char *ename);
|
|
96
104
|
void (*finish)(PInfo pi);
|
|
@@ -151,6 +159,7 @@ struct _pInfo {
|
|
|
151
159
|
|
|
152
160
|
extern VALUE ox_parse(char *xml, size_t len, ParseCallbacks pcb, char **endp, Options options, Err err);
|
|
153
161
|
extern void _ox_raise_error(const char *msg, const char *xml, const char *current, const char *file, int line);
|
|
162
|
+
extern void ox_circ_array_free(CircArray ca);
|
|
154
163
|
|
|
155
164
|
extern void ox_sax_define(void);
|
|
156
165
|
|
|
@@ -205,6 +214,7 @@ extern ID ox_start_element_id;
|
|
|
205
214
|
extern ID ox_string_id;
|
|
206
215
|
extern ID ox_text_id;
|
|
207
216
|
extern ID ox_to_c_id;
|
|
217
|
+
extern ID ox_utc_offset_id;
|
|
208
218
|
extern ID ox_value_id;
|
|
209
219
|
|
|
210
220
|
extern rb_encoding *ox_utf8_encoding;
|