The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#!./perl
#
#  Copyright (c) 2002 Slaven Rezic
#
#  You may redistribute only under the same terms as Perl 5, as specified
#  in the README file that comes with the distribution.
#


use Test::More;
use B::Deparse v0.61;
use File::Spec v0.8;

plan skip_all => "Fix B::Deparse to produce valid code";
plan tests => 59;

use Storable < qw(retrieve store nstore freeze nfreeze thaw dclone);
use Safe;

#$Storable::DEBUGME = 1;

our ($freezed, $thawed, @obj, @res, $blessed_code);

$blessed_code = bless sub { "blessed" }, "Some::Package";
do { package Another::Package; sub foo { __PACKAGE__ } };

do {
    sub code { "JAPH" }
};

local *FOO;

@obj =
    @(\@(\&code,                   # code reference
      sub { 6*7 },
      $blessed_code,            # blessed code reference
      \&Another::Package::foo,  # code in another package
      sub ($x, $y) { 0 },         # prototypes
      sub { print $^STDOUT, "test\n" },
      \&Test::ok,               # large scalar
     ),

     \%("a" => sub { "srt" }, "b" => \&code),

     sub { ord("a")-ord("7") },

     \&code,

     \&dclone,                 # XS function

     sub { open my $foo, "/" },
    );

$Storable::Deparse = 1;
$Storable::Eval    = 1;

######################################################################
# Test freeze & thaw

$freezed = freeze @obj[0];
$thawed  = thaw $freezed;

ok($thawed->[0]->(), "JAPH");
ok($thawed->[1]->(), 42);
ok($thawed->[2]->(), "blessed");
ok($thawed->[3]->(), "Another::Package");
ok(prototype($thawed->[4]), prototype(@obj[0]->[4]));

######################################################################

$freezed = freeze @obj[1];
$thawed  = thaw $freezed;

ok($thawed->{?"a"}->(), "srt");
ok($thawed->{?"b"}->(), "JAPH");

######################################################################

$freezed = freeze @obj[2];
$thawed  = thaw $freezed;

ok($thawed->(), 42);

######################################################################

$freezed = freeze @obj[3];
$thawed  = thaw $freezed;

ok($thawed->(), "JAPH");

######################################################################

try { $freezed = freeze @obj[4] };
ok($^EVAL_ERROR, qr/The result of B::Deparse::coderef2text was empty/);

######################################################################
# Test dclone

my $new_sub = dclone(@obj[2]);
ok($new_sub->(), @obj[2]->());

######################################################################
# Test retrieve & store

store @obj[0], 'store';
$thawed = retrieve 'store';

ok($thawed->[0]->(), "JAPH");
ok($thawed->[1]->(), 42);
ok($thawed->[2]->(), "blessed");
ok($thawed->[3]->(), "Another::Package");
ok(prototype($thawed->[4]), prototype(@obj[0]->[4]));

######################################################################

nstore @obj[0], 'store';
$thawed = retrieve 'store';
unlink 'store';

ok($thawed->[0]->(), "JAPH");
ok($thawed->[1]->(), 42);
ok($thawed->[2]->(), "blessed");
ok($thawed->[3]->(), "Another::Package");
ok(prototype($thawed->[4]), prototype(@obj[0]->[4]));

######################################################################
# Security with
#   $Storable::Eval
#   $Storable::Deparse

do {
    local $Storable::Eval = 0;

    for my $i (0 .. 1) {
	$freezed = freeze @obj[$i];
	$^EVAL_ERROR = "";
	try { $thawed  = thaw $freezed };
	ok($^EVAL_ERROR, qr/Can\'t eval/);
    }
};

do {

    local $Storable::Deparse = 0;
    for my $i (0 .. 1) {
	$^EVAL_ERROR = "";
	try { $freezed = freeze @obj[$i] };
	ok($^EVAL_ERROR, qr/Can\'t store CODE items/);
    }
};

do {
    local $Storable::Eval = 0;
    local $Storable::forgive_me = 1;
    for my $i (0 .. 4) {
	$freezed = freeze @obj[0]->[$i];
	$^EVAL_ERROR = "";
	try { $thawed  = thaw $freezed };
	ok($^EVAL_ERROR, "");
	ok($$thawed, qr/^sub/);
    }
};

do {
    local $Storable::Deparse = 0;
    local $Storable::forgive_me = 1;

    my $devnull = File::Spec->devnull;

    open(my $saverr, ">&", $^STDERR);
    open($^STDERR, ">", $devnull) or
	( print $saverr, "Unable to redirect STDERR: $^OS_ERROR\n" and exit(1) );

    try { $freezed = freeze @obj[0]->[0] };

    open($^STDERR, ">&", \*$saverr);

    ok($^EVAL_ERROR, "");
    ok($freezed ne '');
};

do {
    my $safe = Safe->new();
    local $Storable::Eval = sub { $safe->reval(shift) };

    $freezed = freeze @obj[0]->[0];
    $^EVAL_ERROR = "";
    try { $thawed = thaw $freezed };
    ok($^EVAL_ERROR, "");
    ok($thawed->(), "JAPH");

    $freezed = freeze @obj[0]->[6];
    try { $thawed = thaw $freezed };
    # The "Code sub ..." error message only appears if Log::Agent is installed
    ok($^EVAL_ERROR, qr/(trapped|Code sub)/);

    if (0) {
	# Disable or fix this test if the internal representation of Storable
	# changes.
	skip("no malicious storable file check", 1);
    } else {
	# Construct malicious storable code
	$freezed = nfreeze @obj[0]->[0];
	my $bad_code = ';open FOO, "/badfile"';
	# 5th byte is (short) length of scalar
	my $len = ord(substr($freezed, 4, 1));
	substr($freezed, 4, 1, chr($len+length($bad_code)));
	substr($freezed, -1, 0, $bad_code);
	$^EVAL_ERROR = "";
	try { $thawed = thaw $freezed };
	ok($^EVAL_ERROR, qr/(trapped|Code sub)/);
    }
};

do {
    my $safe = Safe->new();
    # because of opcodes used in "use strict":
    $safe->permit( <qw(:default require caller));
    local $Storable::Eval = sub { $safe->reval(shift) };

    $freezed = freeze @obj[0]->[1];
    $^EVAL_ERROR = "";
    try { $thawed = thaw $freezed };
    ok($^EVAL_ERROR, "");
    ok($thawed->(), 42);
};

do {
    do {
	package MySafe;
	sub new { bless \%(), shift }
	sub reval {
	    my $source = @_[1];
	    # Here you can apply some nifty regexpes to ensure the
	    # safeness of the source code.
	    my $coderef = eval $source;
	    $coderef;
	}
    };

    my $safe = MySafe->new();
    local $Storable::Eval = sub { $safe->reval(@_[0]) };

    $freezed = freeze @obj[0];
    try { $thawed  = thaw $freezed };
    ok($^EVAL_ERROR, "");

    if ($^EVAL_ERROR ne "") {
        ok(0) for @( ( <1..5));
    } else {
	ok($thawed->[0]->(), "JAPH");
	ok($thawed->[1]->(), 42);
	ok($thawed->[2]->(), "blessed");
	ok($thawed->[3]->(), "Another::Package");
	ok(prototype($thawed->[4]), prototype(@obj[0]->[4]));
    }
};

do {
    # Check internal "seen" code
    my $short_sub = sub { "short sub" }; # for SX_SCALAR
    # for SX_LSCALAR
    my $long_sub_code = 'sub { "' . "x"x255 . '" }';
    my $long_sub = eval $long_sub_code; die $^EVAL_ERROR if $^EVAL_ERROR;
    my $sclr = \1;

    local $Storable::Deparse = 1;
    local $Storable::Eval     = 1;

    for my $sub (@($short_sub, $long_sub)) {
	my $res;

	$res = thaw < freeze \@($sub, $sub);
	ok(int($res->[0]), int($res->[1]));

	$res = thaw < freeze \@($sclr, $sub, $sub, $sclr);
	ok(int($res->[0]), int($res->[3]));
	ok(int($res->[1]), int($res->[2]));

	$res = thaw < freeze \@($sub, $sub, $sclr, $sclr);
	ok(int($res->[0]), int($res->[1]));
	ok(int($res->[2]), int($res->[3]));
    }

};