diff --git a/bin/yamlpp-load b/bin/yamlpp-load index 7b50e8bf..b85f0a73 100755 --- a/bin/yamlpp-load +++ b/bin/yamlpp-load @@ -16,6 +16,7 @@ GetOptions( 'duplicate-keys' => \my $duplicate_keys, 'merge' => \my $merge, 'catchall' => \my $catchall, + 'require-footer' => \my $require_footer, 'perl' => \my $perl, 'module|M=s' => \my $module, 'yaml-version=s' => \my $yaml_version, @@ -78,6 +79,7 @@ sub _yamlpp { duplicate_keys => $duplicate_keys ? 1 : 0, preserve => PRESERVE_ORDER, yaml_version => \@yaml_versions, + require_footer => $require_footer, ); my @docs = $ypp->load_string($yaml); return @docs; @@ -142,6 +144,7 @@ Options: --duplicate-keys Allow duplicate keys --merge Enable loading merge keys '<<' --catchall Ignore any unknown tags + --require-footer Require '...' and the end of each document --perl Enable loading perl types and objects (use only on trusted input!) --module -M YAML::PP (default), YAML, YAML::PP::LibYAML, diff --git a/bin/yamlpp-load-dump b/bin/yamlpp-load-dump index c689fbae..2aaf0b76 100755 --- a/bin/yamlpp-load-dump +++ b/bin/yamlpp-load-dump @@ -22,6 +22,7 @@ GetOptions( 'footer!' => \my $footer, 'merge' => \my $merge, 'catchall' => \my $catchall, + 'require-footer' => \my $require_footer, 'perl' => \my $perl, 'preserve|P=s' => \my $preserve, 'module|M=s' => \my $module, @@ -144,6 +145,7 @@ sub _yamlpp { footer => $footer ? 1 : 0, yaml_version => \@yaml_versions, version_directive => $version_directive || 0, + require_footer => $require_footer, ); if ($inc) { $inc->yp($ypp); @@ -292,6 +294,7 @@ Options: --[no-]footer Print '...' --merge Enable loading merge keys '<<' --catchall Ignore any unknown tags + --require-footer Require '...' and the end of each document --perl Enable loading perl types and objects (use only on trusted input!) --preserve, -P Comma separated: 'order', 'scalar', 'flow', 'alias'. diff --git a/lib/YAML/PP.pm b/lib/YAML/PP.pm index 98e5a757..4c3779b5 100644 --- a/lib/YAML/PP.pm +++ b/lib/YAML/PP.pm @@ -30,6 +30,7 @@ sub new { my $writer = delete $args{writer}; my $header = delete $args{header}; my $footer = delete $args{footer}; + my $require_footer = delete $args{require_footer}; my $duplicate_keys = delete $args{duplicate_keys}; my $yaml_version = $class->_arg_yaml_version(delete $args{yaml_version}); my $default_yaml_version = $yaml_version->[0]; @@ -69,6 +70,7 @@ sub new { default_yaml_version => $default_yaml_version, preserve => $preserve, duplicate_keys => $duplicate_keys, + require_footer => $require_footer, ); my $dumper = YAML::PP::Dumper->new( schema => $default_schema, @@ -667,6 +669,29 @@ This option is for dumping. Print document footer C<...> +=item require_footer + +Default: 0 + +Will require a C<...> at the end of each document. +This can be useful in a context where you want to make sure you received +the complete content, for example over network. + + # Good + --- + a: 1 + ... + --- + a: 2 + ... + + # Bad + --- + a: 1 + --- + a: 2 + + =item yaml_version Since version 0.020 diff --git a/lib/YAML/PP/Constructor.pm b/lib/YAML/PP/Constructor.pm index 0a6d471e..bf79de43 100644 --- a/lib/YAML/PP/Constructor.pm +++ b/lib/YAML/PP/Constructor.pm @@ -25,6 +25,7 @@ sub new { unless (defined $duplicate_keys) { $duplicate_keys = 0; } + my $require_footer = delete $args{require_footer}; my $preserve = delete $args{preserve} || 0; if ($preserve == 1) { $preserve = PRESERVE_ORDER | PRESERVE_SCALAR_STYLE | PRESERVE_FLOW_STYLE | PRESERVE_ALIAS; @@ -44,6 +45,7 @@ sub new { cyclic_refs => $cyclic_refs, preserve => $preserve, duplicate_keys => $duplicate_keys, + require_footer => $require_footer, }, $class; $self->init; return $self; @@ -89,6 +91,7 @@ sub preserve_scalar_style { return $_[0]->{preserve} & PRESERVE_SCALAR_STYLE } sub preserve_flow_style { return $_[0]->{preserve} & PRESERVE_FLOW_STYLE } sub preserve_alias { return $_[0]->{preserve} & PRESERVE_ALIAS } sub duplicate_keys { return $_[0]->{duplicate_keys} } +sub require_footer { return $_[0]->{require_footer} } sub document_start_event { my ($self, $event) = @_; @@ -118,6 +121,9 @@ sub document_end_event { die "Got unexpected end of document"; } my $docs = $self->docs; + if ($event->{implicit} and $self->require_footer) { + die sprintf "load: Document (%d) did not end with '...' (require_footer=1)", 1 + scalar @$docs; + } push @$docs, $last->{ref}->[0]; $self->set_anchors({}); $self->set_stack([]); diff --git a/lib/YAML/PP/Loader.pm b/lib/YAML/PP/Loader.pm index 40ee96ec..8772c5c3 100644 --- a/lib/YAML/PP/Loader.pm +++ b/lib/YAML/PP/Loader.pm @@ -16,6 +16,7 @@ sub new { my $default_yaml_version = delete $args{default_yaml_version} || '1.2'; my $preserve = delete $args{preserve}; my $duplicate_keys = delete $args{duplicate_keys}; + my $require_footer = delete $args{require_footer}; my $schemas = delete $args{schemas}; $schemas ||= { '1.2' => YAML::PP->default_schema( @@ -29,6 +30,7 @@ sub new { default_yaml_version => $default_yaml_version, preserve => $preserve, duplicate_keys => $duplicate_keys, + require_footer => $require_footer, ); my $parser = delete $args{parser}; unless ($parser) { diff --git a/t/47.header-footer.t b/t/47.header-footer.t index 65d69340..83b4a93b 100644 --- a/t/47.header-footer.t +++ b/t/47.header-footer.t @@ -102,6 +102,52 @@ EOM cmp_ok($out, 'eq', $out_expected, "Dumping with indent"); }; +subtest require_footer => sub { + my $good1 = <<'EOM'; +a: 1 +... +EOM + my $good2 = <<'EOM'; +a: 1 +... +--- +a: 2 +... +EOM + my $bad1 = <<'EOM'; +a: 1 +--- +a: 2 +... +EOM + my $bad2 = <<'EOM'; +a: 1 +... +--- +a: 2 +EOM + my $bad3 = <<'EOM'; +a: 1 +--- +a: 2 +EOM + my $yp = YAML::PP->new( require_footer => 1 ); + my $data; + local $@; + + $data = eval { $yp->load_string($good1) }; + is $@, '', "good 1"; + $data = eval { $yp->load_string($good2) }; + is $@, '', "good 2"; + + my $re = qr{Document .\d+. did not end with '...' .require_footer=1.}; + $data = eval { $yp->load_string($bad1) }; + like $@, $re, "bad 1"; + $data = eval { $yp->load_string($bad1) }; + like $@, $re, "bad 2"; + $data = eval { $yp->load_string($bad1) }; + like $@, $re, "bad 3"; +}; done_testing;