#!/usr/bin/perl
package evil;

use 5.008009;
use strict;
use warnings;

use Carp;

my $INTERMEDIATE = __PACKAGE__.'/intermediate';
my $LAX          = __PACKAGE__.'/lax';

our $VERSION = 0.002;

our %tainted;
our %wants_strict;

sub import {
	croak "Cannot load evil module when \"no evil ':strict'\" is in effect" if %wants_strict;

	my $hinthash = (caller 0)[10] || {};
	croak "Current module requested no evilness" if $hinthash->{$LAX};

	$hinthash = (caller 3)[10] || {};
	croak "Cannot load evil module when parent requested \"no evil ':lax'\"" if $hinthash->{$LAX};

	my $level = 4;
	my @caller;
	while (@caller = caller $level) {
		$hinthash = $caller[10] || {};
		croak "Cannot load evil module when ancestor requested \"no evil ':intermediate'\""
		  if $hinthash->{$INTERMEDIATE};
		$level++;
	}

	$tainted{caller()} = 1;
}

sub unimport {
	my $strict_arg = grep /^:strict$/i, @_;
	my $intermediate_arg = grep /^:intermediate$/i, @_;
	my $lax_arg = grep /^:lax$/i, @_;
	my $disable_arg = grep /^:disable$/i, @_;

	if (!$disable_arg && $tainted{caller()}) { # caller is evil
		croak 'Current module is evil'
	}

	if ($strict_arg) {
		$wants_strict{caller()} = 1;
		croak "Evil module already loaded. Cannot enforce \"no evil ':strict'\"" if %tainted
	} elsif ($lax_arg) {
		$^H{$LAX} = 1
	} elsif ($disable_arg) {
		delete $wants_strict{caller()};
		delete $^H{$LAX};
		delete $^H{$INTERMEDIATE};
	} else { # $intermediate_arg or no arg
		$^H{$INTERMEDIATE} = $^H{$LAX} = 1
	}
}

1;
__END__

=encoding utf-8

=head1 NAME

evil - RFC 3514 (evil bit) implementation for Perl modules

=head1 SYNOPSIS

  # in A.pm
  package A;
  use evil;
  ...

  # in B.pm
  package B;
  no evil ':strict';
  use A; # <dies>
  ...


=head1 DESCRIPTION

L<RFC3514|https://www.ietf.org/rfc/rfc3514.txt> introduces a new flag
called the "evil bit" in all IP packets. The intention is to simplify
the work of firewalls. Software that sends IP packets with malicious
intent must set the evil bit to true, and firewalls can simply drop
such packets.

The evil pragma is a Perl implementation of the same concept. With
this pragma malicious modules can declare their evil intent while
critical modules can request that they will only use / run alongside
non-evil code.

The pragma can be used in the following ways:

=over

=item use B<evil>;

Marks the current package as evil. All malicious modules MUST use this
directive to ensure the full functionality of this module.

=item no B<evil> ':strict';

The calling module function properly if malignant code is loaded
anywhere in the program. Throws an exception if an evil module is
loaded, whether at the moment of calling this pragma or in the future.

=item no B<evil> ':disable';

Removes the effect of any previous C<no B<evil> ':strict'>. In other
words evil modules will now be allowed to be loaded.

=item no B<evil> ':intermediate'; (TODO)

Not yet implemented. The calling module cannot function properly if it
is using evil code, whether directly or indirectly. Throws an
exception if an evil module is loaded by the calling module or by one
of the children modules (or by one of their children modules, etc).

=item no B<evil> ':lax'; (TODO)

Not yet implemented. The calling module cannot function properly if it
is using evil code direcly. Throws an exception if the calling module
loads an evil module.

=item no B<evil>;

This would normally be equivalent to C<no evil ':intermediate';> but
since that mode is not yet implemented this call does the same as
C<no evil ':strict';> while also emitting a warning saying that this
behaviour will change in a future version.

=back

=head1 AUTHOR

Marius Gavrilescu, E<lt>marius@ieval.roE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2016 by Marius Gavrilescu

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.22.2 or,
at your option, any later version of Perl 5 you may have available.


=cut
