File Coverage

blib/lib/JSON/Schema/Modern/Utilities.pm
Criterion Covered Total %
statement 484 505 95.8
branch 236 280 84.2
condition 157 188 83.5
subroutine 73 77 94.8
pod 21 36 58.3
total 971 1086 89.4


line stmt bran cond sub pod time code
1 46     46   248 use strict;
  46         80  
  46         1441  
2 46     46   161 use warnings;
  46         71  
  46         2893  
3             package JSON::Schema::Modern::Utilities;
4             # vim: set ts=8 sts=2 sw=2 tw=100 et :
5             # ABSTRACT: Internal utilities for JSON::Schema::Modern
6              
7             our $VERSION = '0.641';
8              
9 46     46   581 use 5.020;
  46         127  
10 46     46   156 use strictures 2;
  46         238  
  46         1284  
11 46     46   13980 use stable 0.031 'postderef';
  46         527  
  46         199  
12 46     46   5270 use experimental 'signatures';
  46         71  
  46         163  
13 46     46   1778 no autovivification warn => qw(fetch store exists delete);
  46         73  
  46         191  
14 46     46   2625 use if "$]" >= 5.022, experimental => 're_strict';
  46         63  
  46         706  
15 46     46   2408 use if "$]" < 5.025002, experimental => 'lexical_subs';
  46         63  
  46         1264  
16 46     46   196 no if "$]" >= 5.031009, feature => 'indirect';
  46         67  
  46         1882  
17 46     46   201 no if "$]" >= 5.033001, feature => 'multidimensional';
  46         99  
  46         1599  
18 46     46   210 no if "$]" >= 5.033006, feature => 'bareword_filehandles';
  46         57  
  46         1595  
19 46     46   172 no if "$]" >= 5.041009, feature => 'smartmatch';
  46         59  
  46         1321  
20 46     46   165 no feature 'switch';
  46         60  
  46         841  
21 46     46   168 use B;
  46         59  
  46         1044  
22 46     46   138 use Carp qw(carp croak);
  46         67  
  46         2252  
23 46     46   198 use builtin::compat qw(blessed created_as_number);
  46         68  
  46         447  
24 46     46   6267 use Scalar::Util 'looks_like_number';
  46         74  
  46         2354  
25 46     46   196 use if "$]" < 5.041010, 'List::Util' => 'any';
  46         63  
  46         1392  
26 46     46   149 use if "$]" >= 5.041010, experimental => 'keyword_any';
  46         81  
  46         495  
27 46     46   2479 use Clone 'clone';
  46         73  
  46         1886  
28 46     46   186 use Feature::Compat::Try;
  46         77  
  46         377  
29 46     46   1743 use Mojo::JSON ();
  46         79  
  46         554  
30 46     46   170 use Mojo::JSON::Pointer ();
  46         76  
  46         564  
31 46     46   144 use JSON::PP ();
  46         66  
  46         784  
32 46     46   145 use Types::Standard qw(Str InstanceOf Enum);
  46         68  
  46         298  
33 46     46   101550 use Mojo::File 'path';
  46         108  
  46         2214  
34 46     46   199 use namespace::clean;
  46         59  
  46         202  
35              
36 46     46   11239 use Exporter 'import';
  46         100  
  46         3994  
37              
38             our @EXPORT_OK = qw(
39             is_type
40             get_type
41             is_bool
42             is_schema
43             is_bignum
44             is_equal
45             is_elements_unique
46             jsonp
47             unjsonp
48             jsonp_get
49             jsonp_elements
50             jsonp_set
51             local_annotations
52             canonical_uri
53             E
54             A
55             abort
56             assert_keyword_exists
57             assert_keyword_type
58             assert_pattern
59             assert_uri_reference
60             assert_uri
61             annotate_self
62             sprintf_num
63             true
64             false
65             json_pointer_type
66             canonical_uri_type
67             core_types_type
68             core_formats_type
69             register_schema
70             load_cached_document
71             add_media_type
72             delete_media_type
73             decode_media_type
74             encode_media_type
75             match_media_type
76             );
77              
78 46     46   209 use constant HAVE_BUILTIN => "$]" >= 5.035010;
  46         87  
  46         3453  
79 46     46   207 use if HAVE_BUILTIN, experimental => 'builtin';
  46         63  
  46         590  
80              
81 46     46   2160 use constant _BUILTIN_BOOLS => 0;
  46         68  
  46         4809  
82             use constant {
83             _BUILTIN_BOOLS && HAVE_BUILTIN
84 46         789 && Mojo::JSON::JSON_XS && eval { Cpanel::JSON::XS->VERSION(4.38); 1 }
85             ? (true => builtin::true, false => builtin::false)
86             : (true => JSON::PP::true, false => JSON::PP::false)
87 46     46   227 };
  46         69  
88              
89             # Mojo::JSON::JSON_XS is false when the environment variable $MOJO_NO_JSON_XS is set
90             # and also checks if Cpanel::JSON::XS is installed.
91             # Mojo::JSON falls back to its own pure-perl encoder/decoder but does not support all the options
92             # that we require here.
93             use constant _JSON_BACKEND =>
94 46         540 Mojo::JSON::JSON_XS && eval { Cpanel::JSON::XS->VERSION('4.38'); 1 } ? 'Cpanel::JSON::XS'
  46         14352  
95 46 0       229 : eval { JSON::PP->VERSION('4.11'); 1 } ? 'JSON::PP'
  0 50       0  
  0         0  
96 46     46   5417 : croak 'Cpanel::JSON::XS 4.38 or JSON::PP 4.11 is required';
  46         200  
97              
98             # supports the six core types, plus integer (which is also a number)
99             # we do NOT check stringy_numbers here -- you must do that in the caller
100             # note that sometimes a value may return true for more than one type, e.g. integer+number,
101             # or number+string, depending on its internal flags.
102             # pass { legacy_ints => 1 } in $config to use draft4 integer behaviour
103             # behaviour is consistent with get_type() (where integers are also numbers).
104 87648     87648 1 927755 sub is_type ($type, $value, $config = {}) {
  87648         103790  
  87648         111476  
  87648         114819  
  87648         97727  
105 87648 100       147352 if ($type eq 'null') {
106 113         421 return !(defined $value);
107             }
108 87535 100       135538 if ($type eq 'boolean') {
109 11231         22798 return is_bool($value);
110             }
111 76304 100       119027 if ($type eq 'object') {
112 19901         78561 return ref $value eq 'HASH';
113             }
114 56403 100       96298 if ($type eq 'array') {
115 14093         58626 return ref $value eq 'ARRAY';
116             }
117              
118 42310 100 100     114498 if ($type eq 'string' or $type eq 'number' or $type eq 'integer') {
      100        
119 42294 100       64908 return 0 if not defined $value;
120 42274         192218 my $flags = B::svref_2object(\$value)->FLAGS;
121              
122             # dualvars with the same string and (stringified) numeric value could be either a string or a
123             # number, and before 5.36 we can't tell the difference, so we will answer yes for both.
124             # in 5.36+, stringified numbers still get a PV but don't have POK set, whereas
125             # numified strings do have POK set, so we can tell which one came first.
126              
127 42274 100       84574 if ($type eq 'string') {
128             # like created_as_string, but rejects dualvars with stringwise-unequal string and numeric parts
129             return !length ref($value)
130             && !(HAVE_BUILTIN && builtin::is_bool($value))
131             && $flags & B::SVf_POK
132             && (!($flags & (B::SVf_IOK | B::SVf_NOK))
133 46   100 46   256 || do { no warnings 'numeric'; 0+$value eq $value });
  46         68  
  46         22600  
  30307         255506  
134             }
135              
136 11967 100       25111 if ($type eq 'number') {
137             # floats in json will always be parsed into Math::BigFloat, when allow_bignum is enabled
138 8048   100     16331 return is_bignum($value) || created_as_number($value);
139             }
140              
141 3919 50       8891 if ($type eq 'integer') {
142 3919 100       10229 if ($config->{legacy_ints}) {
143             # in draft4, an integer is "A JSON number without a fraction or exponent part.",
144             # therefore 2.0 is NOT an integer
145 17   100     120 return ref($value) eq 'Math::BigInt'
146             || ($flags & B::SVf_IOK) && !($flags & B::SVf_NOK) && created_as_number($value);
147             }
148             else {
149             # note: values that are larger than $Config{ivsize} will be represented as an NV, not IV,
150             # therefore they will fail this check -- which is why use of Math::BigInt is recommended
151             # if the exact type is important, or loss of any accuracy is unacceptable
152 3902   100     8065 return is_bignum($value) && $value->is_int
153             # if dualvar, PV and stringified NV/IV must be identical
154             || created_as_number($value) && int($value) == $value;
155             }
156             }
157             }
158              
159 16 100       68 if ($type =~ /^reference to (.+)\z/) {
160 11   33     78 return !blessed($value) && ref($value) eq $1;
161             }
162              
163 5         24 return ref($value) eq $type;
164             }
165              
166             # returns one of the six core types, plus integer
167             # we do NOT check stringy_numbers here -- you must do that in the caller
168             # pass { legacy_ints => 1 } in $config to use draft4 integer behaviour
169             # behaviour is consistent with is_type().
170 123434     123434 1 710952 sub get_type ($value, $config = {}) {
  123434         146992  
  123434         151139  
  123434         135495  
171 123434 100       310881 return 'object' if ref $value eq 'HASH';
172 48478 100       72113 return 'boolean' if is_bool($value);
173 37078 100       72053 return 'null' if not defined $value;
174 36434 100       56848 return 'array' if ref $value eq 'ARRAY';
175              
176             # floats in json will always be parsed into Math::BigFloat, when allow_bignum is enabled
177 30409 100       49351 if (length(my $ref = ref $value)) {
178             return $ref eq 'Math::BigInt' ? 'integer'
179 880 100 100     5340 : $ref eq 'Math::BigFloat' ? (!$config->{legacy_ints} && $value->is_int ? 'integer' : 'number')
    100          
    100          
    100          
180             : (defined blessed($value) ? '' : 'reference to ').$ref;
181             }
182              
183 29529         90514 my $flags = B::svref_2object(\$value)->FLAGS;
184              
185             # dualvars with the same string and (stringified) numeric value could be either a string or a
186             # number, and before 5.36 we can't tell the difference, so we choose number because it has been
187             # evaluated as a number already.
188             # in 5.36+, stringified numbers still get a PV but don't have POK set, whereas
189             # numified strings do have POK set, so we can tell which one came first.
190              
191             # like created_as_string, but rejects dualvars with stringwise-unequal string and numeric parts
192             return 'string'
193             if $flags & B::SVf_POK
194             && (!($flags & (B::SVf_IOK | B::SVf_NOK))
195 46 100 100 46   290 || do { no warnings 'numeric'; 0+$value eq $value });
  46   100     70  
  46         92075  
  29529         99541  
196              
197 6940 100       14395 if ($config->{legacy_ints}) {
198             # in draft4, an integer is "A JSON number without a fraction or exponent part.",
199             # therefore 2.0 is NOT an integer
200 316 100 66     1896 return ($flags & B::SVf_IOK) && !($flags & B::SVf_NOK) ? 'integer' : 'number'
    50          
201             if created_as_number($value);
202             }
203             else {
204             # note: values that are larger than $Config{ivsize} will be represented as an NV, not IV,
205             # therefore they will fail this check -- which is why use of Math::BigInt is recommended
206             # if the exact type is important, or loss of any accuracy is unacceptable
207 6624 100       30593 return int($value) == $value ? 'integer' : 'number' if created_as_number($value);
    100          
208             }
209              
210             # this might be a scalar with POK|IOK or POK|NOK set
211 15         49 return 'ambiguous type';
212             }
213              
214             # lifted from JSON::MaybeXS
215             # note: unlike builtin::compat::is_bool on older perls, we do not accept
216             # dualvar(0,"") or dualvar(1,"1") because JSON::PP and Cpanel::JSON::XS
217             # do not encode these as booleans.
218 60118     60118 1 63571 sub is_bool ($value) {
  60118         67423  
  60118         61583  
219 60118 100 66     289027 HAVE_BUILTIN and builtin::is_bool($value)
      66        
220             or
221             !!blessed($value)
222             and ($value->isa('JSON::PP::Boolean')
223             or $value->isa('Cpanel::JSON::XS::Boolean')
224             or $value->isa('JSON::XS::Boolean'));
225             }
226              
227 34783     34783 1 41802 sub is_schema ($value) {
  34783         43106  
  34783         36333  
228 34783 100       126437 ref $value eq 'HASH' || is_bool($value);
229             }
230              
231 13509     13509 0 16085 sub is_bignum ($value) {
  13509         17342  
  13509         15873  
232 13509         108747 ref($value) =~ /^Math::Big(?:Int|Float)\z/;
233             }
234              
235             # compares two arbitrary data payloads for equality, as per
236             # https://json-schema.org/draft/2020-12/json-schema-core.html#rfc.section.4.2.2
237             # $state hashref supports the following fields:
238             # - scalarref_booleans (input): treats \0 and \1 as boolean values
239             # - stringy_numbers (input): strings will also be compared numerically
240             # - path (output): location of the first difference
241             # - error (output): description of the first difference
242 17318     17318 1 143803 sub is_equal ($x, $y, $state = {}) {
  17318         21011  
  17318         19506  
  17318         19791  
  17318         18052  
243 17318   100     44106 $state->{path} //= '';
244              
245 17318         27705 my @types = map get_type($_), $x, $y;
246              
247 17318 100       39229 $state->{error} = 'ambiguous type encountered', return 0
248             if grep $types[$_] eq 'ambiguous type', 0..1;
249              
250 17315 100       31861 if ($state->{scalarref_booleans}) {
251 88 100       154 ($x, $types[0]) = (0+!!$$x, 'boolean') if $types[0] eq 'reference to SCALAR';
252 88 100       157 ($y, $types[1]) = (0+!!$$y, 'boolean') if $types[1] eq 'reference to SCALAR';
253             }
254              
255 17315 100       28686 if ($state->{stringy_numbers}) {
256 16 100 100     93 ($x, $types[0]) = (0+$x, int(0+$x) == $x ? 'integer' : 'number')
    100          
257             if $types[0] eq 'string' and looks_like_number($x);
258              
259 16 100 100     60 ($y, $types[1]) = (0+$y, int(0+$y) == $y ? 'integer' : 'number')
    100          
260             if $types[1] eq 'string' and looks_like_number($y);
261             }
262              
263 17315 100       30951 $state->{error} = "wrong type: $types[0] vs $types[1]", return 0 if $types[0] ne $types[1];
264 15910 100       23151 return 1 if $types[0] eq 'null';
265 15874 100 100     42828 ($x eq $y and return 1), $state->{error} = 'strings not equal', return 0
266             if $types[0] eq 'string';
267 6372 100 100     23735 ($x == $y and return 1), $state->{error} = "$types[0]s not equal", return 0
268             if grep $types[0] eq $_, qw(boolean number integer);
269              
270 4266         6714 my $path = $state->{path};
271 4266 100       6997 if ($types[0] eq 'object') {
272 1828 100       4837 $state->{error} = 'property count differs: '.keys(%$x).' vs '.keys(%$y), return 0
273             if keys %$x != keys %$y;
274              
275 1726 100       9963 if (not is_equal(my $arr_x = [ sort keys %$x ], my $arr_y = [ sort keys %$y ], my $s={})) {
276 11         34 my $pos = substr($s->{path}, 1);
277 11         454 $state->{error} = 'property names differ starting at position '.$pos.' ("'.$arr_x->[$pos].'" vs "'.$arr_y->[$pos].'")';
278 11         100 return 0;
279             }
280              
281 1715         5427 foreach my $property (sort keys %$x) {
282 4022         6011 $state->{path} = jsonp($path, $property);
283 4022 100       7867 return 0 if not is_equal($x->{$property}, $y->{$property}, $state);
284             }
285              
286 1528         8799 return 1;
287             }
288              
289 2438 50       4363 if ($types[0] eq 'array') {
290 2438 100       4492 $state->{error} = 'element count differs: '.@$x.' vs '.@$y, return 0 if @$x != @$y;
291 2427         5595 foreach my $idx (0 .. $x->$#*) {
292 5667         9923 $state->{path} = $path.'/'.$idx;
293 5667 100       10973 return 0 if not is_equal($x->[$idx], $y->[$idx], $state);
294             }
295 2229         13827 return 1;
296             }
297              
298 0         0 $state->{error} = 'got surprising type: '.$types[0], return 0; # should never get here
299             }
300              
301             # checks array elements for uniqueness. short-circuits on first pair of matching elements
302             # $state hashref supports the following fields:
303             # - scalarref_booleans (input): treats \0 and \1 as boolean values
304             # - stringy_numbers (input): strings will also be compared numerically
305             # - path (output): location of the first difference
306             # - error (output): description of the first difference
307             # - equal_indices (output): the indices of identical items
308 3609     3609 1 4977 sub is_elements_unique ($array, $state = {}) {
  3609         4904  
  3609         5023  
  3609         4316  
309 3609         10878 foreach my $idx0 (0 .. $array->$#*-1) {
310 1467         3884 foreach my $idx1 ($idx0+1 .. $array->$#*) {
311 2123 100       6131 if (is_equal($array->[$idx0], $array->[$idx1], $state)) {
312 280 50       4054 push $state->{equal_indices}->@*, $idx0, $idx1 if exists $state->{equal_indices};
313 280         1094 return 0;
314             }
315             }
316             }
317 3329         10829 return 1;
318             }
319              
320             # shorthand for creating and appending json pointers
321             # the first argument is an already-encoded json pointer; remaining arguments are path segments to be
322             # encoded and appended
323             sub jsonp {
324 51664 50 66 51664 1 222301 carp q{first argument to jsonp should be '' or start with '/'} if length($_[0]) and substr($_[0],0,1) ne '/';
325 51664         399463 return join('/', shift, map s!~!~0!gr =~ s!/!~1!gr, grep defined, @_);
326             }
327              
328             # splits a json pointer apart into its path segments
329             sub unjsonp {
330 0 0 0 0 1 0 carp q{argument to unjsonp should be '' or start with '/'} if length($_[0]) and substr($_[0],0,1) ne '/';
331 0         0 return map s!~0!~!gr =~ s!~1!/!gr, split m!/!, $_[0];
332             }
333              
334 0     0 1 0 sub jsonp_get ($data, $pointer) {
  0         0  
  0         0  
  0         0  
335 0         0 Mojo::JSON::Pointer->new($data)->get($pointer);
336             }
337              
338             # flatten the data structure into a hashref of { pointer => value, ... }
339             # (essentially the reverse of jsonp_set($data, $foo->%{$_}) foreach keys $foo)
340 21     21 1 5372 sub jsonp_elements ($data, $prefix = '') {
  21         25  
  21         26  
  21         21  
341             # recursively walk the structure..
342             my $hash = +{
343             ref $data eq '' ? ($prefix => $data)
344 21 50       134 : ref $data eq 'HASH' ? map jsonp_elements($data->{$_}, $prefix.'/'.$_)->%*, keys %$data
    100          
    100          
345             : ref $data eq 'ARRAY' ? map jsonp_elements($data->[$_], $prefix.'/'.$_)->%*, 0..$data->$#*
346             : croak 'unrecognized type: '. ref $data
347             };
348             }
349              
350             # assigns a value to a data structure at a specific json pointer location
351             # operates destructively, in place, unless the root data or type is being modified
352 17470     17470 1 42643 sub jsonp_set ($data, $pointer, $value) {
  17470         28156  
  17470         25632  
  17470         23822  
  17470         21949  
353 17470 100 100     101210 croak 'cannot write into a non-reference in void context'
354             if not grep ref $data eq $_, qw(HASH ARRAY) and not defined wantarray;
355              
356             # assigning to the root overwrites existing data
357 17468 100       43724 if (not length $pointer) {
358 17412 100 100     49689 if (not ref $data or ref $data ne ref $value) {
359 17410 100       82108 return $value if defined wantarray;
360 2         202 croak 'cannot write into a reference of a different type in void context';
361             }
362              
363 2 100       6 if (ref $value eq 'HASH') {
364 1 50       3 $data = {} if not ref $data;
365 1         4 $data->%* = $value->%*;
366             }
367 2 100       5 if (ref $value eq 'ARRAY') {
368 1 50       3 $data = [] if not ref $data;
369 1         3 $data->@* = $value->@*;
370             }
371              
372 2         5 return $data;
373             }
374              
375 56 50       324 my @keys = map +(s!~0!~!gr =~ s!~1!/!gr),
376             (length $pointer ? (split /\//, $pointer, -1) : ($pointer));
377              
378 56 100 66     525 croak 'cannot write a hashref into a reference to an array in void context'
      100        
      66        
379             if @keys >= 2 and $keys[1] !~ /^(?:\d+|-)\z/a and ref $data eq 'ARRAY' and not defined wantarray;
380              
381 55         78 shift @keys; # always '', indicating the root
382 55         86 my $curp = \$data;
383              
384 55         83 foreach my $key (@keys) {
385             # if needed, first remove the existing data so we can replace with a new hash key or array index
386 113 100 100     346 undef $curp->$*
      100        
387             if not ref $curp->$*
388             or ref $curp->$* eq 'ARRAY' and $key !~ /^(?:\d+|-)\z/a;
389              
390             # use this existing hash key or array index location, or create new position
391 46     46   341 use autovivification 'store';
  46         62  
  46         252  
392             $curp = \(
393 113 100 100     425 ref $curp->$* eq 'HASH' || $key !~ /^(?:\d+|-)\z/a ? $curp->$*->{$key}
    100          
394             : $key =~ /^\d+\z/a ? $curp->$*->[$key]
395             : $curp->$*->[$curp->$*->$#* + 1]);
396             }
397              
398 55         89 $curp->$* = $value;
399 55         265 return $data;
400             }
401              
402             # returns a reusable Types::Standard type for json pointers
403             # TODO: move this off into its own distribution, see JSON::Schema::Types
404 368     368 1 100355 sub json_pointer_type () { Str->where('!length || m{^/} && !m{~(?![01])}'); }
  368         567  
  368         1165  
405              
406             # a URI without a fragment, or with a json pointer fragment
407 92     92 1 259473 sub canonical_uri_type () {
  92         186  
408 92         452 (InstanceOf['Mojo::URL'])->where(q{!defined($_->fragment) || $_->fragment =~ m{^/} && $_->fragment !~ m{~(?![01])}});
409             }
410              
411             # Validation §7.1-2: "Note that the "type" keyword in this specification defines an "integer" type
412             # which is not part of the data model. Therefore a format attribute can be limited to numbers, but
413             # not specifically to integers."
414 92     92 1 125093 sub core_types_type () {
  92         140  
415 92         407 Enum[qw(null object array boolean string number)];
416             }
417              
418 4     4 1 10 sub core_formats_type () {
  4         9  
419 4         41 Enum[qw(date-time date time duration email idn-email hostname idn-hostname ipv4 ipv6 uri uri-reference iri iri-reference uuid uri-template json-pointer relative-json-pointer regex)];
420             }
421              
422             # simple runtime-wide cache of $ids to schema document objects that are sourced from disk
423             {
424             my $document_cache = {};
425              
426             # Fetches a document from the cache (reading it from disk and creating the document if necessary),
427             # and add it to the evaluator.
428             # Normally this will just be a cache of schemas that are bundled with this distribution or a related
429             # distribution (such as OpenAPI-Modern), as duplicate identifiers are not checked for, unlike for
430             # normal schema additions.
431             # Only JSON-encoded files are supported at this time.
432 114     114 1 210 sub load_cached_document ($evaluator, $uri) {
  114         194  
  114         189  
  114         190  
433 114         328 $uri =~ s/#\z//; # older draft $ids use an empty fragment
434              
435             # see if it already exists as a document in the cache
436 114         19804 my $document = $document_cache->{$uri};
437              
438             # otherwise, load it from disk using our filename cache and create the document
439 114 100 100     19389 if (not $document and my $filename = get_schema_filename($uri)) {
440 68         11138 my $file = path($filename);
441 68 50       1651 croak "uri $uri maps to file $file which does not exist" if not -f $file;
442 68         4610 my $schema = $evaluator->_json_decoder->decode($file->slurp);
443              
444             # avoid calling add_schema, which checksums the file to look for duplicates
445 68         14304 $document = JSON::Schema::Modern::Document->new(
446             schema => $schema,
447             evaluator => $evaluator,
448             skip_ref_checks => 1,
449             );
450              
451             # avoid calling add_document, which checks for duplicate identifiers (and would result in an
452             # infinite loop)
453 68 50       314 die JSON::Schema::Modern::Result->new(
454             output_format => $evaluator->output_format,
455             valid => 0,
456             errors => [ $document->errors ],
457             exception => 1,
458             ) if $document->has_errors;
459              
460 68         441 $document_cache->{$uri} = $document;
461             }
462              
463 114 100       18788 return if not $document;
464              
465             # bypass the normal collision checks, to avoid an infinite loop: these documents are presumed safe
466 103         429 $evaluator->_add_resources_unsafe(
467             map +($_->[0] => +{ $_->[1]->%*, document => $document }),
468             $document->resource_pairs
469             );
470              
471 103         46348 return $document;
472             }
473             }
474              
475             ###### media-type mayhem below!
476              
477             {
478             # a hashref that indexes a media-type or media-range string to a decoder, encoder, and
479             # denormalized representation of the string.
480             # prepopulated list must match the list of definitions in _predefined_media_types
481             my $MEDIA_TYPES = {
482             map +(join('/', @$_) => { type => $_->[0], subtype => $_->[1] }), (
483             [ qw(application json) ],
484             [ qw(application octet-stream) ],
485             [ qw(text *) ],
486             [ qw(application x-www-form-urlencoded) ],
487             [ qw(application x-ndjson) ],
488             # multipart/form-data and multipart/* are special-cased in OpenAPI::Modern: do not add here
489             )
490             };
491              
492             # see RFC9110 §8.3.1 for ABNF
493             my $OWS = q{[\x09\x20]*};
494             my $TOKEN = q{[a-zA-Z0-9!#$%&'*+.^_`|~-]+};
495             my $QUOTED_STRING = q{"((?:[\x09\20\x21\x23-\x5B\x5D-\x7E\x80-\xFF]|\x5C[\x09\x20-\x7E\x80-\xFF])*)"};
496              
497             # parses into hashref: { type => .., subtype => .., params => { .. } }
498 211     211   215 my sub _parse_media_type ($media_type_string) {
  211         243  
  211         193  
499 211         1028 my ($type_subtype, @params) = split /$OWS;$OWS/, $media_type_string;
500 211   50     1292 my ($type, $subtype) = ($type_subtype//'') =~ m{^($TOKEN)/($TOKEN)\z};
501 211 100 66     544 return if not defined $type or not defined $subtype;
502              
503             # RFC9110 §5.6.4: "The backslash octet ("\") can be used as a single-octet quoting mechanism
504             # within quoted-string and comment constructs. Recipients that process the value of a
505             # quoted-string MUST handle a quoted-pair as if it were replaced by the octet following the
506             # backslash."
507 209 100       823 my $params = {
    50          
508             map +(m{^($TOKEN)=($TOKEN|$QUOTED_STRING)\z}
509             ? (fc($1) => defined $3 ? ($3 =~ s/\x5C(.)/$1/gr) : $2)
510             : ()),
511             @params
512             };
513              
514             # some parameter values are case-insensitive; enumerate them here
515 209         508 $params->{$_} = fc($params->{$_}) foreach grep exists $params->{$_}, qw(charset);
516              
517 209 50       368 croak 'cannot parse more than 64 parameters' if keys $params->%* > 64;
518             +{
519 209 100       818 type => fc($type),
520             subtype => fc($subtype),
521             keys %$params ? (parameters => $params) : (),
522             };
523             }
524              
525             # wrapped in a sub so we don't define them until needed
526 25     25   31 my sub _predefined_media_types ($media_type_string) {
  25         29  
  25         29  
527             return +{
528             type => 'application',
529             subtype => 'json',
530             # UTF-8 decoding and encoding is always done, as per the JSON spec.
531             # other charsets are not supported: see RFC8259 §11
532             decode => sub ($content_ref, @) {
533 22         429 \ _JSON_BACKEND->new->allow_nonref(1)->utf8(1)->decode($content_ref->$*);
534             },
535             encode => sub ($content_ref, @) {
536 3         43 \ _JSON_BACKEND->new->allow_nonref(1)->utf8(1)->allow_blessed(1)->convert_blessed(1)->encode($content_ref->$*);
537             },
538 25 100       88 owner_addr => 1,
539             }
540             if $media_type_string eq 'application/json';
541              
542             return +{
543             type => 'application',
544             subtype => 'octet-stream',
545 21 50       32 (map +($_ => sub ($content_ref, @) { $content_ref }), qw(decode encode)),
  0         0  
546             owner_addr => 1,
547             }
548             if $media_type_string eq 'application/octet-stream';
549              
550             # identity function, with charset support
551             return +{
552             type => 'text', subtype => '*',
553             decode => sub ($content_ref, $parameters = {}, @) {
554             # RFC2046 §4.1.2: charset is case-insensitive
555             return $parameters->{charset} ?
556 5 100       36 \ Encode::decode($parameters->{charset}, $content_ref->$*, Encode::DIE_ON_ERR | Encode::LEAVE_SRC)
557             : $content_ref;
558             },
559             encode => sub ($content_ref, $parameters = {}, @) {
560             return $parameters->{charset} ?
561 2 50       14 \ Encode::encode($parameters->{charset}, $content_ref->$*, Encode::DIE_ON_ERR | Encode::LEAVE_SRC)
562             : $content_ref;
563             },
564 21 100       52 owner_addr => 1,
565             }
566             if $media_type_string eq 'text/*';
567              
568             return +{
569             type => 'application',
570             subtype => 'x-www-form-urlencoded',
571             decode => sub ($content_ref, @) {
572 3         32 \ Mojo::Parameters->new->charset('UTF-8')->parse($content_ref->$*)->to_hash;
573             },
574             encode => sub ($content_ref, @) {
575             \ Mojo::Parameters->new->charset('UTF-8')
576 2         7 ->parse(map +($_ => $content_ref->$*->{$_}), sort keys $content_ref->$*->%*)->to_string;
577             },
578 19 100       50 owner_addr => 1,
579             }
580             if $media_type_string eq 'application/x-www-form-urlencoded';
581              
582             return +{
583             type => 'application',
584             subtype => 'x-ndjson',
585             decode => sub ($content_ref, @) {
586 3         41 my $decoder = _JSON_BACKEND->new->allow_nonref(1)->utf8(1);
587 3         5 my $line = 0; # line numbers start at 1
588             \[ map {
589 3         22 do {
  7         8  
590 7         10 try { ++$line; $decoder->decode($_) }
  7         7  
  7         62  
591 1         145 catch ($e) { croak 'parse error at line '.$line.': '.$e }
592             }
593             }
594             split(/\r?\n/, $content_ref->$*)
595             ];
596             },
597             encode => sub ($content_ref, @) {
598 1         10 my $encoder = _JSON_BACKEND->new->allow_nonref(1)->utf8(1)->allow_blessed(1)->convert_blessed(1);
599 1         29 \ join "\n", map $encoder->encode($_), $content_ref->$*->@*;
600             },
601 17 100       51 owner_addr => 1,
602             }
603             if $media_type_string eq 'application/x-ndjson';
604             }
605              
606             # for internal use by JSON::Schema::Modern only! may be removed without notice!
607 24     24   29 sub _get_media_type_decoder ($media_type_string) {
  24         29  
  24         36  
608 24         50 my $matched_string = match_media_type($media_type_string);
609 24 100       66 return undef if not defined $matched_string;
610              
611 20         35 my $definition = $MEDIA_TYPES->{$matched_string};
612             $definition = $MEDIA_TYPES->{$matched_string} = _predefined_media_types($matched_string)
613 20 100       61 if not exists $definition->{decode};
614              
615 20         151 return $definition->{decode};
616             }
617              
618 16     16 1 885 sub add_media_type ($media_type_string, $decoder_sub = undef, $encoder_sub = undef, $owner_addr = 1) {
  16         23  
  16         18  
  16         21  
  16         34  
  16         16  
619 16 50 66     43 croak 'decoder is not a subref' if defined $decoder_sub and ref $decoder_sub ne 'CODE';
620 16 50 33     35 croak 'encoder is not a subref' if defined $encoder_sub and ref $encoder_sub ne 'CODE';
621              
622 16         26 my $type = _parse_media_type($media_type_string);
623 16 100       186 croak "bad media-type string \"$media_type_string\"" if not $type;
624              
625             # populate the cache if it's a bundled type that hasn't been defined yet
626 15 50       43 _predefined_media_types($media_type_string) if not exists $MEDIA_TYPES->{$media_type_string};
627              
628 15 100       31 if (any { is_equal($type, { $_->%{qw(type subtype parameters)} }) } values %$MEDIA_TYPES) {
  134         301  
629 1 50       117 croak 'duplicate media-type found' if $owner_addr == 1;
630              
631             # track evaluator object that used the deprecated add_media_type interface
632 0         0 push $MEDIA_TYPES->{$media_type_string}{owner_addr}->@*, $owner_addr;
633 0         0 return;
634             }
635              
636 14         69 $MEDIA_TYPES->{$media_type_string} = {
637             decode => $decoder_sub,
638             encode => $encoder_sub,
639             %$type,
640             owner_addr => [ $owner_addr ], # refaddr of the evaluator object that added us
641             };
642              
643 14         35 return;
644             }
645              
646 7     7 1 1014 sub delete_media_type ($media_type_string, $owner_addr = 1) {
  7         11  
  7         8  
  7         9  
647 7 100       29 return if not exists $MEDIA_TYPES->{$media_type_string};
648              
649             delete $MEDIA_TYPES->{$media_type_string}
650             if $owner_addr == 1
651             or $MEDIA_TYPES->{$media_type_string}{owner_addr} = [
652             grep +($_ != 1 && $_ != $owner_addr), $MEDIA_TYPES->{$media_type_string}{owner_addr}->@*
653 5 50 33     48 ];
      100        
654             }
655              
656             # wildcards, parameters supported
657             # always returns a reference to the decoded data, or undef if no decoder is found
658 30     30 1 11132 sub decode_media_type ($media_type_string, $content_ref) {
  30         46  
  30         44  
  30         34  
659 30 0       81 croak 'decoder payload must be a reference to a string (not ',
    50          
660             (length ref $content_ref ? ref $content_ref : 'a non-reference')
661             if ref $content_ref ne 'SCALAR';
662              
663 30         84 my $matched_string = match_media_type($media_type_string);
664 30 100       66 return if not $matched_string;
665              
666 28         54 my $definition = $MEDIA_TYPES->{$matched_string};
667             $definition = $MEDIA_TYPES->{$matched_string} = _predefined_media_types($matched_string)
668 28 100       84 if not exists $definition->{decode};
669              
670 28 100       78 return if not $definition->{decode};
671              
672 27         62 my $type = _parse_media_type($media_type_string);
673 27   66     149 $definition->{decode}->($content_ref, $type->{parameters}//());
674             }
675              
676             # wildcards, parameters supported
677 9     9 1 1497 sub encode_media_type ($media_type_string, $content_ref) {
  9         13  
  9         9  
  9         9  
678 9 50 66     27 croak 'encoder payload must be a reference' if ref $content_ref ne 'REF' and ref $content_ref ne 'SCALAR';
679              
680 9         19 my $matched_string = match_media_type($media_type_string);
681 9 50       15 return if not $matched_string;
682              
683 9         13 my $definition = $MEDIA_TYPES->{$matched_string};
684             $definition = $MEDIA_TYPES->{$matched_string} = _predefined_media_types($matched_string)
685 9 50       17 if not exists $definition->{encode};
686              
687 9 100       19 return if not $definition->{encode};
688              
689 8         14 my $type = _parse_media_type($media_type_string);
690 8   66     29 $definition->{encode}->($content_ref, $type->{parameters}//());
691             }
692              
693             # finds best match for a media-type against a list of media-types. if parameter(s) are included in
694             # the media-type to be matched, all parameters must be present in the match value.
695 90     90 1 485948 sub match_media_type ($media_type_string, $media_types = []) {
  90         119  
  90         111  
  90         151  
696 90 50       186 return if not length $media_type_string;
697              
698             # return immediately if exact match exists
699             return $media_type_string
700 115         303 if @$media_types and any { $_ eq $media_type_string } @$media_types
701 90 100 100     531 or not @$media_types and exists $MEDIA_TYPES->{$media_type_string};
      100        
      100        
702              
703 50         94 my $mt = _parse_media_type($media_type_string);
704 50 100       88 return if not $mt;
705              
706 49 100       88 my $types = @$media_types ? +{ map +($_ => _parse_media_type($_)), @$media_types } : $MEDIA_TYPES;
707              
708 49         60 my @matches; # [ rank, candidate ]
709              
710             # iterate through each provided MT and compare it to the string for matchability..
711             CANDIDATE:
712 49         169 foreach my $candidate (keys %$types) {
713             # if candidate has parameters, all parameters must match; missing parameters ok.
714             # the more parameters match the higher the score.
715 523         532 my $params_matched = 0;
716 523   100     1018 foreach my $param (keys(($types->{$candidate}{parameters}//{})->%*)) {
717             next CANDIDATE if not exists(($mt->{parameters}//{})->{$param})
718 130 100 100     387 or $types->{$candidate}{parameters}{$param} ne $mt->{parameters}{$param};
      100        
719              
720 26         33 ++$params_matched;
721             }
722              
723 419 100       573 push(@matches, [ 0+$params_matched, $candidate ]), next if $candidate eq '*/*';
724             push(@matches, [ 2**8 + $params_matched, $candidate ]), next
725             if $types->{$candidate}{subtype} eq '*'
726 391 100 100     811 and $types->{$candidate}{type} eq $mt->{type};
727              
728             # exact type + subtype match: best overall
729 356 100       627 if ($types->{$candidate}{type} eq $mt->{type}) {
730             push(@matches, [ 2**10 + $params_matched, $candidate ]), next
731 80 100       161 if $types->{$candidate}{subtype} eq $mt->{subtype};
732              
733             # text/foo+plain matches text/plain but not text/bar+plain
734             push(@matches, [ 2**9 + $params_matched, $candidate ]), next
735 56 100 100     133 if $mt->{subtype} =~ m{^.+\+(.+)\z} and $types->{$candidate}{subtype} eq $1;
736             }
737             }
738              
739 49 100       101 return if not @matches;
740 43         112 my @sorted = sort { $b->[0] <=> $a->[0] } @matches;
  62         121  
741 43         211 return $sorted[0]->[1];
742             }
743             }
744              
745             ######## NO PUBLIC INTERFACES FOLLOW THIS POINT ########
746              
747             # get all annotations produced for the current instance data location (that are visible to this
748             # schema location) - remember these are hashrefs, not Annotation objects
749 1395     1395 0 1775 sub local_annotations ($state) {
  1395         2115  
  1395         1835  
750 1395         6679 grep $_->{instance_location} eq $state->{data_path}, $state->{annotations}->@*;
751             }
752              
753             # shorthand for finding the current uri of the present schema location
754             # ensure that this code is kept consistent with the absolute_keyword_location builder in ResultNode
755             # Note that this may not be canonical if keyword_path has not yet been reset via the processing of a
756             # local identifier keyword (e.g. '$id').
757 34245     34245 0 46089 sub canonical_uri ($state, @extra_path) {
  34245         42503  
  34245         40583  
  34245         38422  
758 34245 100 66     156949 return $state->{initial_schema_uri} if not @extra_path and not length($state->{keyword_path});
759 12799         47118 my $uri = $state->{initial_schema_uri}->clone;
760 12799 50 100     835329 my $fragment = ($uri->fragment//'').(@extra_path ? jsonp($state->{keyword_path}, @extra_path) : $state->{keyword_path});
761 12799 50       91892 undef $fragment if not length($fragment);
762 12799         28617 $uri->fragment($fragment);
763 12799         65097 $uri;
764             }
765              
766             # shorthand for creating error objects
767             # uses these keys from $state:
768             # - initial_schema_uri
769             # - keyword (optional)
770             # - data_path
771             # - traversed_keyword_path
772             # - keyword_path
773             # - _keyword_path_suffix (optional)
774             # - errors
775             # - exception (optional; set by abort())
776             # - recommended_response (optional)
777             # - depth
778             # - traverse (boolean, used for mode)
779             # returns defined-false, so callers can use 'return;' to differentiate between
780             # failed-with-no-error from failed-with-error.
781 13954     13954 0 49628 sub E ($state, $error_string, @args) {
  13954         19301  
  13954         20160  
  13954         22344  
  13954         17040  
782 13954 50       27878 croak 'E called in void context' if not defined wantarray;
783              
784             # sometimes the keyword shouldn't be at the very end of the schema path
785 13954         32417 my $sps = delete $state->{_keyword_path_suffix};
786 13954 100 100     57852 my @keyword_path_suffix = defined $sps && ref $sps eq 'ARRAY' ? $sps->@* : $sps//();
      100        
787              
788             # we store the absolute uri in unresolved form until needed,
789             # and perform the rest of the calculations later.
790 13954   100     60499 my $uri = [ $state->@{qw(initial_schema_uri keyword_path)}, $state->{keyword}//(), @keyword_path_suffix ];
791              
792             my $keyword_location = $state->{traversed_keyword_path}
793 13954   100     58432 .jsonp($state->{keyword_path}, $state->{keyword}//(), @keyword_path_suffix);
794              
795 13954         89352 require JSON::Schema::Modern::Error;
796             push $state->{errors}->@*, JSON::Schema::Modern::Error->new(
797             depth => $state->{depth} // 0,
798             keyword => $state->{keyword},
799             $state->{traverse} ? () : (instance_location => $state->{data_path}),
800             keyword_location => $keyword_location,
801             # we calculate absolute_keyword_location when instantiating the Error object for Result
802             _uri => $uri,
803             error => @args ? sprintf($error_string, @args) : $error_string,
804             exception => $state->{exception},
805             ($state->%{recommended_response})x!!$state->{recommended_response},
806 13954 100 50     432483 mode => $state->{traverse} ? 'traverse' : 'evaluate',
    100          
    100          
807             );
808              
809 13954         82594 return 0;
810             }
811              
812             # shorthand for creating annotations
813             # uses these keys from $state:
814             # - initial_schema_uri
815             # - keyword (mandatory)
816             # - data_path
817             # - traversed_keyword_path
818             # - keyword_path
819             # - annotations
820             # - collect_annotations
821             # - _unknown (boolean)
822             # - depth
823 12028     12028 0 15526 sub A ($state, $annotation) {
  12028         15282  
  12028         15942  
  12028         15510  
824             # even if the user requested annotations, we only collect them for later drafts
825             # ..but we always collect them if the lowest bit is set, indicating the presence of unevaluated*
826             # keywords necessary for accurate validation
827             return 1 if not ($state->{collect_annotations}
828 12028 100       64404 & ($state->{specification_version} =~ /^draft[467]\z/ ? ~(1<<8) : ~0));
    100          
829              
830             # we store the absolute uri in unresolved form until needed,
831             # and perform the rest of the calculations later.
832 2638         8465 my $uri = [ $state->@{qw(initial_schema_uri keyword_path keyword)} ];
833              
834 2638         8278 my $keyword_location = $state->{traversed_keyword_path}.jsonp($state->@{qw(keyword_path keyword)});
835              
836             push $state->{annotations}->@*, {
837             depth => $state->{depth} // 0,
838             keyword => $state->{keyword},
839             instance_location => $state->{data_path},
840             keyword_location => $keyword_location,
841             # we calculate absolute_keyword_location when instantiating the Annotation object for Result
842             _uri => $uri,
843             annotation => $annotation,
844 2638 100 50     21228 $state->{_unknown} ? (unknown => 1) : (),
845             };
846              
847 2638         5539 return 1;
848             }
849              
850             # creates an error object, but also aborts evaluation immediately
851             # only this error is returned, because other errors on the stack might not actually be "real"
852             # errors (consider if we were in the middle of evaluating a "not" or "if").
853             # Therefore this is only appropriate during the evaluation phase, not the traverse phase.
854 34     34 0 234 sub abort ($state, $error_string, @args) {
  34         66  
  34         50  
  34         64  
  34         46  
855 34         374 ()= E({ %$state, exception => 1 }, $error_string, @args);
856 34 50       210 croak 'abort() called during traverse' if $state->{traverse};
857 34         403 die pop $state->{errors}->@*;
858             }
859              
860 0     0 0 0 sub assert_keyword_exists ($state, $schema) {
  0         0  
  0         0  
  0         0  
861 0 0       0 croak 'assert_keyword_exists called in void context' if not defined wantarray;
862 0 0       0 return E($state, '%s keyword is required', $state->{keyword}) if not exists $schema->{$state->{keyword}};
863 0         0 return 1;
864             }
865              
866 42348     42348 0 50460 sub assert_keyword_type ($state, $schema, $type) {
  42348         52321  
  42348         47198  
  42348         52652  
  42348         46200  
867 42348 50       70361 croak 'assert_keyword_type called in void context' if not defined wantarray;
868 42348 100       109685 return 1 if is_type($type, $schema->{$state->{keyword}});
869 17 100       111 E($state, '%s value is not a%s %s', $state->{keyword}, ($type =~ /^[aeiou]/ ? 'n' : ''), $type);
870             }
871              
872 2875     2875 0 4770 sub assert_pattern ($state, $pattern) {
  2875         4132  
  2875         4501  
  2875         3573  
873 2875 50       6346 croak 'assert_pattern called in void context' if not defined wantarray;
874 2875         4826 try {
875 2875     0   21198 local $SIG{__WARN__} = sub { die @_ };
  0         0  
876 2875         42607 qr/$pattern/;
877             }
878 3         10 catch ($e) { return E($state, $e); };
879 2872         18245 return 1;
880             }
881              
882             # this is only suitable for checking URIs within schemas themselves
883             # note that we cannot use $state->{specification_version} to more tightly constrain the plain-name
884             # fragment syntax, as we could be checking a $ref to a schema using a different version
885 5886     5886 0 7903 sub assert_uri_reference ($state, $schema) {
  5886         6932  
  5886         7053  
  5886         6705  
886 5886 50       9633 croak 'assert_uri_reference called in void context' if not defined wantarray;
887              
888 5886         13911 my $string = $schema->{$state->{keyword}};
889             return E($state, '%s value is not a valid URI-reference', $state->{keyword})
890             # see also uri-reference format sub
891 5886 100 66     21520 if fc(Mojo::URL->new($string)->to_unsafe_string) ne fc($string)
      100        
      100        
      100        
      100        
892             or $string =~ /[^[:ascii:]]/ # ascii characters only
893             or $string =~ /#/ # no fragment, except...
894             and $string !~ m{#\z} # allow empty fragment
895             and $string !~ m{#[A-Za-z_][A-Za-z0-9_:.-]*\z} # allow plain-name fragment, superset of all drafts
896             and $string !~ m{#/(?:[^~]|~[01])*\z}; # allow json pointer fragment
897              
898 5852         1739682 return 1;
899             }
900              
901             # this is only suitable for checking URIs within schemas themselves,
902             # which have fragments consisting of plain names (anchors) or json pointers
903 6199     6199 0 7629 sub assert_uri ($state, $schema, $override = undef) {
  6199         8101  
  6199         9014  
  6199         8785  
  6199         7314  
904 6199 50       12011 croak 'assert_uri called in void context' if not defined wantarray;
905              
906 6199   33     12090 my $string = $override // $schema->{$state->{keyword}};
907 6199         20944 my $uri = Mojo::URL->new($string);
908              
909 6199 100 66     453229 return E($state, '"%s" is not a valid URI', $string)
      100        
      100        
      66        
      66        
      66        
910             # see also uri format sub
911             if fc($uri->to_unsafe_string) ne fc($string)
912             or $string =~ /[^[:ascii:]]/ # ascii characters only
913             or not $uri->is_abs # must have a scheme
914             or $string =~ /#/ # no fragment, except...
915             and $string !~ m{#\z} # empty fragment
916             and $string !~ m{#[A-Za-z][A-Za-z0-9_:.-]*\z} # plain-name fragment
917             and $string !~ m{#/(?:[^~]|~[01])*\z}; # json pointer fragment
918              
919 6183         1079136 return 1;
920             }
921              
922             # produces an annotation whose value is the same as that of the current schema keyword
923             # makes a copy as this is passed back to the user, who cannot be trusted to not mutate it
924 1138     1138 0 1503 sub annotate_self ($state, $schema) {
  1138         1436  
  1138         1357  
  1138         1368  
925             A($state, ref $schema->{$state->{keyword}} ? clone($schema->{$state->{keyword}})
926 1138 100       5727 : $schema->{$state->{keyword}});
927             }
928              
929             # use original value as stored in the NV, without losing precision
930 1305     1305 0 2092 sub sprintf_num ($value) {
  1305         2097  
  1305         2099  
931 1305 100       2646 is_bignum($value) ? $value->bstr : sprintf('%s', $value);
932             }
933              
934             {
935             # simple runtime-wide cache of $ids to filenames that are sourced from disk
936             my $schema_filename_cache = {};
937              
938             # adds a mapping from a URI to an absolute filename in the global runtime
939             # (available to all instances of the evaluator running in the same process).
940 966     966 0 993 sub register_schema ($uri, $filename) {
  966         984  
  966         988  
  966         893  
941 966         2712 $schema_filename_cache->{$uri} = $filename;
942             }
943              
944 2001     2001 0 2623 sub get_schema_filename ($uri) {
  2001         2624  
  2001         2509  
945 2001         6513 $schema_filename_cache->{$uri};
946             }
947             }
948              
949             1;
950              
951             __END__