#! /usr/bin/perl -CDSAL # Emit an HTML page with the obscured text. # Obfuscations: # # - Greek Alpha for A (doesn’t work--hard to get matching fonts) # - Text is in a ragged table # - -d # - disable keypress using Javascript # - disable mouse click using Javascript # - Spaces are random chars with white-on-white # - CSS for printing: no-display # - Insert random no-display characters # - Three classes: normal, white, no-display # - document.write(reverse …) # - \XX in CSS # - \uXXXX in Javascript # - UTF-7 # - Insert u+200b (zero-width space) # - Decryption depends on current time (control caching) use 5.14.2; use utf8; use List::Util 'max'; use Text::Tabs; use Getopt::Long qw(:config bundling); use Date::Parse; use warnings FATAL => 'all'; $0 =~ s!.*/!!; # Reduce program name to basename sub usage() { die "usage: $0 [-e ] [-p] [file] …\n", " -p: allow printing\n", "example expiration dates (default is six months from now):\n", " -e 2020-12-31\n", " -e \"May 7 2012\"\n", " -e 10:30pm\n", } my ($expire, $xor); # Default: no expiration GetOptions("expire|e=s" => \$expire, "print|p" => \my $printable) or usage(); if ($expire) { my $endpoint = str2time($expire) or die "$0: invalid expiration: $expire\n"; warn "$0: This document will expire on " . localtime($endpoint), "\n"; my $duration = $endpoint - time(); die "$0: expiration date has passed\n" if $duration <= 0; # Say that duration is a year in the future, and today is August 28, 2011. # This means that the document should be good from Aug 28 2011-Aug 28 2012. # $offset will be the number of seconds Jan 1 2011-Aug 28 2011. # $index will be: # Jan 1 1970-Aug 28 1970: -1 # Aug 28 1970-Aug 28 1971: 0 # Aug 28 1971-Aug 28 1972: 1 # Aug 28 1972-Aug 28 1973: 2 # … # Aug 28 2011-Aug 28 2012: 41 # # Our magic number is 41. Scramble every letter with that value. # The decoding Javascript will similarly decode, based on the current time. my $now = time(); my $offset = $now % $duration; my $index = int(($now-$offset)/$duration); $xor = ($index & 0xff) ^ (($index>>8) & 0xff) ^ (($index>>16) & 0xff) ^ (($index>>24) & 0xff); $xor = ($index & 0xff); # TODO: for now } my %alternate = ( "'" => "′", '"' => "″", '-' => "–", # '/' => "⁄", # Don’t try mapping Greek letters, e.g., A -> &Alpha. # It looks terrible on Linux systems. ); # Read the data my @lines = map { tr/\r\n//d; expand($_) } <>; # How long is the longest line? my $width = max map {length} @lines; my $height = @lines; # Pad all lines to the longest line length for (@lines) { $_ .= " " x ($width-length) if length() < $width; } # Convert @lines to a 2-D array for (@lines) { $_ = [split '', $_]; } my @padchars = map {"&#$_;"} grep {chr($_) =~ /[^<&]/} 33..126; sub randchar() { $padchars[rand @padchars]; } my $table = ""; sub add($) { $table .= $_[0]; } # Make a transposed, ragged, table. # Every has the option of spanning several rows. add ""; # If we don’t have this, Internet Explorer & Google Chrome give up halfway # through the table. Perhaps this helps them precompute the dimensions. add ""; add ""; for my $x (0..$width-1) { next unless defined $lines[$y][$x]; # my $colheight = 1+int rand $height-$y; my $span = $height-$y; $span = 20 if $span > 20; my $colheight = 1+int rand $span; # There's a problem where browsers get confused # if a row is completely empty. Workaround: don’t # extend columns in the first column. That way, each # row has at least the first entry. $colheight=1 if $x==0; if ($colheight>1) { # Consider rowspan=0 for all the rest of the rows. add "
" for 1..$width; add "\n"; for my $y (0..$height-1) { add "
"; } else { add ""; } for my $z (0..$colheight-1) { my $c = $lines[$y+$z][$x]; undef $lines[$y+$z][$x]; # Add a random, non-displayed, char. add "" . randchar . "" if rand > 0.1; if ($c eq " ") { add "" . randchar; } elsif ($c eq "_") { add "

 "; } elsif (exists $alternate{$c}) { add "$alternate{$c}"; } else { add "&#" . ord($c) . ";"; } } } add "\n"; } add "

"; my $preamble1 = <<'END' . "\n" x 500 . <<'END'; END # Printing produces a blank page, unless we allow it. my $media_print=""; $media_print = '@media print { body { display: none; } }' unless $printable; my $preamble2 = <<'END' . $media_print . <<'END'; END $table =~ s/(.)/sprintf("\\%o", ord $&)/ge; # my $expires = time() + $duration; my $preamble3 = <<"END"; document.write("$table"); END my $document = $preamble2 . $preamble3 . $postamble; $document =~ s|\s*/\*.*?\*/\s*||g; # Remove comments & surrounding spaces $document =~ s/\s*\n\s*//g; # Remove newlines & surrounding spaces print $preamble1, $document;