2201 lines
66 KiB
Perl
2201 lines
66 KiB
Perl
|
|
#!/usr/bin/perl
|
||
|
|
#
|
||
|
|
# This code was forked from the LiveJournal project owned and operated
|
||
|
|
# by Live Journal, Inc. The code has been modified and expanded by
|
||
|
|
# Dreamwidth Studios, LLC. These files were originally licensed under
|
||
|
|
# the terms of the license supplied by Live Journal, Inc, which can
|
||
|
|
# currently be found at:
|
||
|
|
#
|
||
|
|
# http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt
|
||
|
|
#
|
||
|
|
# In accordance with the original license, this code and all its
|
||
|
|
# modifications are provided under the GNU General Public License.
|
||
|
|
# A copy of that license can be found in the LICENSE file included as
|
||
|
|
# part of this distribution.
|
||
|
|
#
|
||
|
|
# This code was originally imported from:
|
||
|
|
#
|
||
|
|
# http://code.sixapart.com/svn/bml/trunk
|
||
|
|
#
|
||
|
|
# We have copied this module locally to modify it for use in the Dreamwidth project.
|
||
|
|
# Original copyright is presumably owned by Six Apart, Ltd. Modifications are
|
||
|
|
# copyright (C) 2008-2012 by Dreamwidth Studios, LLC.
|
||
|
|
|
||
|
|
use strict;
|
||
|
|
no warnings 'uninitialized';
|
||
|
|
|
||
|
|
package BML::Request;
|
||
|
|
|
||
|
|
use fields qw(
|
||
|
|
env blockref lang r blockflags BlockStack
|
||
|
|
file scratch IncludeOpen content_type clean_package package
|
||
|
|
filechanged scheme scheme_file IncludeStack etag location
|
||
|
|
most_recent_mod stop_flag want_last_modified cookies
|
||
|
|
);
|
||
|
|
|
||
|
|
package Apache::BML;
|
||
|
|
|
||
|
|
use Apache2::Const qw/ :common REDIRECT HTTP_NOT_MODIFIED /;
|
||
|
|
use Apache2::Log ();
|
||
|
|
use Apache2::Request;
|
||
|
|
use Apache2::RequestRec ();
|
||
|
|
use Apache2::RequestUtil ();
|
||
|
|
use Apache2::RequestIO ();
|
||
|
|
use APR::Table;
|
||
|
|
use APR::Finfo ();
|
||
|
|
use Digest::MD5;
|
||
|
|
use File::Spec;
|
||
|
|
use DW::SiteScheme;
|
||
|
|
use LJ::Directories;
|
||
|
|
|
||
|
|
BEGIN {
|
||
|
|
$Apache::BML::HAVE_ZLIB = eval "use Compress::Zlib (); 1;";
|
||
|
|
}
|
||
|
|
|
||
|
|
BEGIN {
|
||
|
|
# So we get better reporiting on failures in BML files
|
||
|
|
$^P |= 0x100;
|
||
|
|
}
|
||
|
|
|
||
|
|
# set per request:
|
||
|
|
use vars qw($cur_req);
|
||
|
|
use vars qw(%CodeBlockOpts);
|
||
|
|
|
||
|
|
# scalar hashrefs of versions below, minus the domain part:
|
||
|
|
my ( $SchemeData, $SchemeFlags );
|
||
|
|
|
||
|
|
# keyed by domain:
|
||
|
|
my $ML_SCOPE; # generally the $apache_r->uri, auto set on each request (unless overridden)
|
||
|
|
my ( %SchemeData, %SchemeFlags )
|
||
|
|
; # domain -> scheme -> key -> scalars (data has {s} blocks expanded)
|
||
|
|
|
||
|
|
# safely global:
|
||
|
|
use vars qw(%FileModTime %LookItems); # LookItems: file -> template -> [ data, flags ]
|
||
|
|
use vars qw(%LookParent); # file -> parent file
|
||
|
|
use vars qw(%LookChild); # file -> child -> 1
|
||
|
|
|
||
|
|
my (%CodeBlockMade);
|
||
|
|
|
||
|
|
use vars qw($conf_pl $conf_pl_look); # hashref, made empty before loading a .pl conf file
|
||
|
|
my %DenyConfig; # filename -> 1
|
||
|
|
our %FileConfig; # filename -> hashref
|
||
|
|
my %FileLastStat; # filename -> time we last looked at its modtime
|
||
|
|
|
||
|
|
use vars qw($base_recent_mod);
|
||
|
|
|
||
|
|
# the request we're handling (BML::get_request()). using this way
|
||
|
|
# instead of just using BML::get_request() because when using
|
||
|
|
# Apache::FakeRequest and non-mod_perl env, I can't seem to get/set
|
||
|
|
# the value of BML::get_request()
|
||
|
|
use vars qw($r);
|
||
|
|
|
||
|
|
# regexps to match open and close tokens. (but old syntax (=..=) is deprecated)
|
||
|
|
my ( $TokenOpen, $TokenClose ) = ( '<\?', '\?>' );
|
||
|
|
|
||
|
|
tie %BML::ML, 'BML::ML';
|
||
|
|
tie %BML::COOKIE, 'BML::Cookie';
|
||
|
|
|
||
|
|
sub handler {
|
||
|
|
|
||
|
|
# get request and store for later
|
||
|
|
my $apache_r = shift;
|
||
|
|
$Apache::BML::r = $apache_r;
|
||
|
|
|
||
|
|
# determine what file we're supposed to work with:
|
||
|
|
my $file = Apache::BML::decide_file_and_stat($apache_r);
|
||
|
|
|
||
|
|
# $file was stat'd by decide_file_and_stat above, so use '_'
|
||
|
|
# FIXME: ModPerl: this is not true in ModPerl 2.0, so we are using $file.
|
||
|
|
unless ( -e $file ) {
|
||
|
|
$apache_r->log_error("File does not exist: $file");
|
||
|
|
return NOT_FOUND;
|
||
|
|
}
|
||
|
|
|
||
|
|
# second time we can use _ though...
|
||
|
|
unless ( -r _ ) {
|
||
|
|
$apache_r->log_error("File permissions deny access: $file");
|
||
|
|
return FORBIDDEN;
|
||
|
|
}
|
||
|
|
|
||
|
|
# load now as this might go away
|
||
|
|
my $modtime = ( stat _ )[9];
|
||
|
|
|
||
|
|
# never serve these
|
||
|
|
return FORBIDDEN if $file =~ /\b_config/;
|
||
|
|
|
||
|
|
# create new request
|
||
|
|
my $req = Apache::BML::initialize_cur_req( $apache_r, $file );
|
||
|
|
|
||
|
|
# setup env
|
||
|
|
my $env = $req->{env};
|
||
|
|
|
||
|
|
# walk up directories, looking for _config.bml files, populating env
|
||
|
|
my $dir = $file;
|
||
|
|
my $docroot = $apache_r->document_root();
|
||
|
|
$docroot =~ s!/$!!;
|
||
|
|
my @dirconfs;
|
||
|
|
my %confwant; # file -> 1, if applicable config
|
||
|
|
|
||
|
|
while ($dir) {
|
||
|
|
$dir =~ s!/[^/]*$!!;
|
||
|
|
my $conffile = "$dir/_config.bml";
|
||
|
|
$confwant{$conffile} = 1;
|
||
|
|
push @dirconfs, load_conffile($conffile);
|
||
|
|
last if $dir eq $docroot;
|
||
|
|
}
|
||
|
|
|
||
|
|
# we now have dirconfs in order from first to apply to last.
|
||
|
|
# but a later one may have a subconfig to override, so
|
||
|
|
# go through those first, keeping track of which configs
|
||
|
|
# are effective
|
||
|
|
my %eff_config;
|
||
|
|
|
||
|
|
foreach my $cfile (@dirconfs) {
|
||
|
|
my $conf = $FileConfig{$cfile};
|
||
|
|
next unless $conf;
|
||
|
|
$eff_config{$cfile} = $conf;
|
||
|
|
if ( $conf->{'SubConfig'} ) {
|
||
|
|
foreach my $sconf ( keys %confwant ) {
|
||
|
|
my $sc = $conf->{'SubConfig'}{$sconf};
|
||
|
|
$eff_config{$cfile} = $sc if $sc;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach my $cfile (@dirconfs) {
|
||
|
|
my $conf = $eff_config{$cfile};
|
||
|
|
next unless $conf;
|
||
|
|
while ( my ( $k, $v ) = each %$conf ) {
|
||
|
|
next if exists $env->{$k} || $k eq "SubConfig";
|
||
|
|
$env->{$k} = $v;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# check if there are overrides in pnotes
|
||
|
|
# wrapped in eval because Apache::FakeRequest doesn't have
|
||
|
|
# pnotes support (as of 2004-04-26 at least)
|
||
|
|
eval {
|
||
|
|
if ( my $or = $apache_r->pnotes('BMLEnvOverride') ) {
|
||
|
|
while ( my ( $k, $v ) = each %$or ) {
|
||
|
|
$env->{$k} = $v;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
# environment loaded at this point
|
||
|
|
|
||
|
|
if ( $env->{'AllowOldSyntax'} ) {
|
||
|
|
( $TokenOpen, $TokenClose ) = ( '(?:<\?|\(=)', '(?:\?>|=\))' );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
( $TokenOpen, $TokenClose ) = ( '<\?', '\?>' );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( exists $env->{'HOOK-force_redirect'} ) {
|
||
|
|
my $redirect_page = eval { $env->{'HOOK-force_redirect'}->( $apache_r->uri ); };
|
||
|
|
if ( defined $redirect_page ) {
|
||
|
|
$apache_r->headers_out->{Location} = $redirect_page;
|
||
|
|
$Apache::BML::r = undef; # no longer valid
|
||
|
|
return REDIRECT;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# mod_rewrite
|
||
|
|
if ( exists $env->{'HOOK-rewrite_filename'} ) {
|
||
|
|
eval {
|
||
|
|
my $new_file = $env->{'HOOK-rewrite_filename'}->( req => $req, env => $env );
|
||
|
|
$file = $new_file if $new_file;
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
# Look for an alternate file, and if it exists, load it instead of the real
|
||
|
|
# one.
|
||
|
|
if ( exists $env->{TryAltExtension} ) {
|
||
|
|
my $ext = $env->{TryAltExtension};
|
||
|
|
|
||
|
|
# Trim a leading dot on the extension to allow '.lj' or 'lj'
|
||
|
|
$ext =~ s{^\.}{};
|
||
|
|
|
||
|
|
# If the file already has an extension, put the alt extension between it
|
||
|
|
# and the rest of the filename like Apache's content-negotiation.
|
||
|
|
if ( $file =~ m{(\.\S+)$} ) {
|
||
|
|
my $newfile = $file;
|
||
|
|
substr( $newfile, -( length $1 ), 0 ) = ".$ext";
|
||
|
|
if ( -e $newfile ) {
|
||
|
|
$modtime = ( stat _ )[9];
|
||
|
|
$file = $newfile;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
elsif ( -e "$file.$ext" ) {
|
||
|
|
$modtime = ( stat _ )[9];
|
||
|
|
$file = "$file.$ext";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Read the source of the file
|
||
|
|
unless ( open F, $file ) {
|
||
|
|
$apache_r->log_error("Couldn't open $file for reading: $!");
|
||
|
|
$Apache::BML::r = undef; # no longer valid
|
||
|
|
return SERVER_ERROR;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $bmlsource;
|
||
|
|
{ local $/ = undef; $bmlsource = <F>; }
|
||
|
|
close F;
|
||
|
|
|
||
|
|
# consider the file's mod time
|
||
|
|
note_mod_time( $req, $modtime );
|
||
|
|
|
||
|
|
# and all the config files:
|
||
|
|
note_mod_time( $req, $Apache::BML::base_recent_mod );
|
||
|
|
|
||
|
|
# if the file changed since we last looked at it, note that
|
||
|
|
if ( !defined $FileModTime{$file} || $modtime > $FileModTime{$file} ) {
|
||
|
|
$FileModTime{$file} = $modtime;
|
||
|
|
$req->{'filechanged'} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# setup cookies
|
||
|
|
*BMLCodeBlock::COOKIE = *BML::COOKIE;
|
||
|
|
BML::reset_cookies();
|
||
|
|
|
||
|
|
# tied interface to BML::ml();
|
||
|
|
*BMLCodeBlock::ML = *BML::ML;
|
||
|
|
|
||
|
|
# parse in data
|
||
|
|
parse_inputs($apache_r);
|
||
|
|
|
||
|
|
%BMLCodeBlock::GET_POTENTIAL_XSS = ();
|
||
|
|
if ( $env->{MildXSSProtection} ) {
|
||
|
|
foreach my $k ( keys %BMLCodeBlock::GET ) {
|
||
|
|
next unless $BMLCodeBlock::GET{$k} =~ /\<|\%3C/i;
|
||
|
|
$BMLCodeBlock::GET_POTENTIAL_XSS{$k} = $BMLCodeBlock::GET{$k};
|
||
|
|
delete $BMLCodeBlock::GET{$k};
|
||
|
|
delete $BMLCodeBlock::FORM{$k};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $env->{'HOOK-startup'} ) {
|
||
|
|
eval { $env->{'HOOK-startup'}->(); };
|
||
|
|
return report_error( $apache_r, "<b>Error running startup hook:</b><br />\n$@" )
|
||
|
|
if $@;
|
||
|
|
}
|
||
|
|
|
||
|
|
# allow a hook to specify extra perl to be used to bootstrap code
|
||
|
|
# blocks... this will be cached here so the hook doesn't need to run
|
||
|
|
# at every code block compilation
|
||
|
|
$BML::CODE_INIT_PERL = "";
|
||
|
|
if ( $env->{'HOOK-codeblock_init_perl'} ) {
|
||
|
|
$BML::CODE_INIT_PERL = eval { $env->{'HOOK-codeblock_init_perl'}->(); };
|
||
|
|
return report_error( $apache_r, "<b>Error running codeblock_init_perl hook:</b><br />\n$@" )
|
||
|
|
if $@;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $scheme =
|
||
|
|
$apache_r->notes->{'bml_use_scheme'}
|
||
|
|
|| $env->{'ForceScheme'}
|
||
|
|
|| $BMLCodeBlock::GET{skin}
|
||
|
|
|| $BMLCodeBlock::GET{'usescheme'}
|
||
|
|
|| $BML::COOKIE{'BMLschemepref'};
|
||
|
|
|
||
|
|
if ( exists $env->{'HOOK-alt_default_scheme'} ) {
|
||
|
|
$scheme ||= eval { $env->{'HOOK-alt_default_scheme'}->($env); };
|
||
|
|
}
|
||
|
|
|
||
|
|
my $default_scheme_override = undef;
|
||
|
|
if ( $env->{'HOOK-default_scheme_override'} ) {
|
||
|
|
$default_scheme_override = eval {
|
||
|
|
$env->{'HOOK-default_scheme_override'}->( $scheme || DW::SiteScheme->default ); };
|
||
|
|
return report_error( $apache_r, "<b>Error running scheme override hook:</b><br />\n$@" )
|
||
|
|
if $@;
|
||
|
|
}
|
||
|
|
|
||
|
|
$scheme ||= $default_scheme_override || DW::SiteScheme->default;
|
||
|
|
|
||
|
|
# now we've made the decision about what scheme to use
|
||
|
|
# -- does a hook want to translate this into another scheme?
|
||
|
|
if ( $env->{'HOOK-scheme_translation'} ) {
|
||
|
|
my $newscheme = eval { $env->{'HOOK-scheme_translation'}->($scheme); };
|
||
|
|
$scheme = $newscheme if $newscheme;
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ( BML::set_scheme($scheme) ) {
|
||
|
|
$scheme = $env->{'ForceScheme'}
|
||
|
|
|| DW::SiteScheme->default;
|
||
|
|
BML::set_scheme($scheme);
|
||
|
|
}
|
||
|
|
|
||
|
|
my $uri = $apache_r->uri;
|
||
|
|
my $path_info = $apache_r->path_info;
|
||
|
|
my $lang_scope = $uri;
|
||
|
|
$lang_scope =~ s/$path_info$//;
|
||
|
|
BML::set_language_scope($lang_scope);
|
||
|
|
my $lang = BML::decide_language();
|
||
|
|
BML::set_language($lang);
|
||
|
|
|
||
|
|
# print on the HTTP header
|
||
|
|
my $html = $env->{'_error'};
|
||
|
|
|
||
|
|
if ( $env->{'HOOK-before_decode'} ) {
|
||
|
|
eval { $env->{'HOOK-before_decode'}->(); };
|
||
|
|
return report_error( $apache_r, "<b>Error running before_decode hook:</b><br />\n$@" )
|
||
|
|
if $@;
|
||
|
|
}
|
||
|
|
|
||
|
|
bml_decode( $req, \$bmlsource, \$html, { DO_CODE => $env->{'AllowCode'} } )
|
||
|
|
unless $html;
|
||
|
|
|
||
|
|
# force out any cookies we have set
|
||
|
|
BML::send_cookies($req);
|
||
|
|
|
||
|
|
$apache_r->pool->cleanup_register( \&reset_codeblock ) if $req->{'clean_package'};
|
||
|
|
|
||
|
|
# internal redirect, if set previously
|
||
|
|
if ( $apache_r->notes->{internal_redir} ) {
|
||
|
|
my $int_redir = DW::Routing->call( uri => $apache_r->notes->{internal_redir} );
|
||
|
|
if ( defined $int_redir ) {
|
||
|
|
|
||
|
|
# we got a match; remove the internal_redir setting, clear the
|
||
|
|
# request cache, and return DECLINED.
|
||
|
|
$apache_r->notes->{internal_redir} = undef;
|
||
|
|
LJ::start_request();
|
||
|
|
return DECLINED;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# redirect, if set previously
|
||
|
|
if ( $req->{'location'} ) {
|
||
|
|
$apache_r->headers_out->{Location} = $req->{'location'};
|
||
|
|
$Apache::BML::r = undef; # no longer valid
|
||
|
|
return REDIRECT;
|
||
|
|
}
|
||
|
|
|
||
|
|
# see if we can save some bandwidth (though we already killed a bunch of CPU)
|
||
|
|
my $etag;
|
||
|
|
if ( exists $req->{'etag'} ) {
|
||
|
|
$etag = $req->{'etag'} if defined $req->{'etag'};
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$etag = Digest::MD5::md5_hex($html);
|
||
|
|
}
|
||
|
|
$etag = '"' . $etag . '"' if defined $etag;
|
||
|
|
|
||
|
|
my $ifnonematch = $apache_r->headers_in->{"If-None-Match"};
|
||
|
|
if ( defined $ifnonematch && defined $etag && $etag eq $ifnonematch ) {
|
||
|
|
$Apache::BML::r = undef; # no longer valid
|
||
|
|
return HTTP_NOT_MODIFIED;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $rootlang = substr( $req->{'lang'}, 0, 2 );
|
||
|
|
unless ( $env->{'NoHeaders'} ) {
|
||
|
|
eval {
|
||
|
|
# this will fail while using Apache::FakeRequest, but that's okay.
|
||
|
|
$apache_r->content_languages( [$rootlang] );
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
my $modtime_http = modified_time($req);
|
||
|
|
|
||
|
|
my $content_type =
|
||
|
|
$req->{'content_type'}
|
||
|
|
|| $env->{'DefaultContentType'}
|
||
|
|
|| "text/html";
|
||
|
|
|
||
|
|
unless ( $env->{'NoHeaders'} ) {
|
||
|
|
my $ims = $apache_r->headers_in->{"If-Modified-Since"};
|
||
|
|
if ( $ims
|
||
|
|
&& !$env->{'NoCache'}
|
||
|
|
&& $ims eq $modtime_http )
|
||
|
|
{
|
||
|
|
$Apache::BML::r = undef; # no longer valid
|
||
|
|
return HTTP_NOT_MODIFIED;
|
||
|
|
}
|
||
|
|
|
||
|
|
$apache_r->content_type($content_type);
|
||
|
|
|
||
|
|
if ( $env->{'NoCache'} ) {
|
||
|
|
$apache_r->headers_out->{"Cache-Control"} = "no-cache";
|
||
|
|
$apache_r->no_cache(1);
|
||
|
|
}
|
||
|
|
|
||
|
|
$apache_r->headers_out->{"Last-Modified"} = $modtime_http
|
||
|
|
if $env->{'Static'} || $req->{'want_last_modified'};
|
||
|
|
|
||
|
|
$apache_r->headers_out->{"Cache-Control"} = "private, proxy-revalidate";
|
||
|
|
$apache_r->headers_out->{"ETag"} = $etag if defined $etag;
|
||
|
|
|
||
|
|
# gzip encoding
|
||
|
|
my $do_gzip = $env->{'DoGZIP'} && $Apache::BML::HAVE_ZLIB;
|
||
|
|
$do_gzip = 0 if $do_gzip && $content_type !~ m!^text/html!;
|
||
|
|
$do_gzip = 0 if $do_gzip && $apache_r->headers_in->{"Accept-Encoding"} !~ /gzip/;
|
||
|
|
my $length = length($html);
|
||
|
|
$do_gzip = 0 if $length < 500;
|
||
|
|
if ($do_gzip) {
|
||
|
|
my $pre_len = $length;
|
||
|
|
$apache_r->notes->{"bytes_pregzip"} = $pre_len;
|
||
|
|
$html = Compress::Zlib::memGzip($html);
|
||
|
|
$length = length($html);
|
||
|
|
$apache_r->headers_out->{'Content-Encoding'} = 'gzip';
|
||
|
|
$apache_r->headers_out->{'Vary'} = 'Accept-Encoding';
|
||
|
|
}
|
||
|
|
$apache_r->headers_out->{'Content-length'} = $length;
|
||
|
|
|
||
|
|
# FIXME: removed in ModPerl 2.0 is that okay? replacement function?
|
||
|
|
#$apache_r->send_http_header();
|
||
|
|
}
|
||
|
|
|
||
|
|
$apache_r->print($html) unless $env->{'NoContent'} || $apache_r->header_only;
|
||
|
|
|
||
|
|
$Apache::BML::r = undef; # no longer valid
|
||
|
|
return OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub decide_file_and_stat {
|
||
|
|
my $apache_r = shift;
|
||
|
|
my $file;
|
||
|
|
if ( ref $apache_r eq "Apache::FakeRequest" ) {
|
||
|
|
|
||
|
|
# for testing. FakeRequest's 'notes' method is busted, always returning
|
||
|
|
# true.
|
||
|
|
$file = $apache_r->filename;
|
||
|
|
stat($file);
|
||
|
|
}
|
||
|
|
elsif ( $file = $apache_r->notes->{"bml_filename"} ) {
|
||
|
|
|
||
|
|
# when another handler needs to invoke BML directly
|
||
|
|
stat($file);
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# normal case - $apache_r->filename is already stat'd
|
||
|
|
$file = $apache_r->filename;
|
||
|
|
$apache_r->finfo;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $file;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub is_initialized {
|
||
|
|
return $Apache::BML::cur_req ? 1 : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub initialize_cur_req {
|
||
|
|
my $apache_r = shift;
|
||
|
|
my $file = shift;
|
||
|
|
|
||
|
|
my $req = $cur_req = fields::new('BML::Request');
|
||
|
|
$req->{file} = $file || Apache::BML::decide_file_and_stat($apache_r);
|
||
|
|
$req->{r} = $apache_r;
|
||
|
|
$req->{BlockStack} = [""];
|
||
|
|
$req->{scratch} = {}; # _CODE blocks can play
|
||
|
|
$req->{cookies} = {};
|
||
|
|
$req->{env} = {};
|
||
|
|
|
||
|
|
return $req;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub clear_cur_req {
|
||
|
|
return $Apache::BML::cur_req = undef;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub report_error {
|
||
|
|
my $apache_r = shift;
|
||
|
|
my $err = shift;
|
||
|
|
|
||
|
|
$apache_r->content_type("text/html");
|
||
|
|
|
||
|
|
# FIXME: ModPerl: doesn't seem to be used/required anymore
|
||
|
|
#$apache_r->send_http_header();
|
||
|
|
$apache_r->print($err);
|
||
|
|
|
||
|
|
return OK; # TODO: something else?
|
||
|
|
}
|
||
|
|
|
||
|
|
sub file_dontcheck {
|
||
|
|
my $file = shift;
|
||
|
|
my $now = time;
|
||
|
|
return 1 if $FileLastStat{$file} > $now - 10;
|
||
|
|
my $realmod = ( stat($file) )[9];
|
||
|
|
$FileLastStat{$file} = $now;
|
||
|
|
return 1 if $FileModTime{$file} && $realmod == $FileModTime{$file};
|
||
|
|
$FileModTime{$file} = $realmod;
|
||
|
|
return 1 if !$realmod;
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub load_conffile {
|
||
|
|
my ($ffile) = @_; # abs file to load
|
||
|
|
die "can't have dollar signs in filenames" if index( $ffile, '$' ) != -1;
|
||
|
|
die "not absolute path" unless File::Spec->file_name_is_absolute($ffile);
|
||
|
|
my ( $volume, $dirs, $file ) = File::Spec->splitpath($ffile);
|
||
|
|
|
||
|
|
# see which configs are denied
|
||
|
|
my $apache_r = $Apache::BML::r;
|
||
|
|
if ( $apache_r->dir_config("BML_denyconfig") && !%DenyConfig ) {
|
||
|
|
my $docroot = $apache_r->document_root();
|
||
|
|
my $deny = $apache_r->dir_config("BML_denyconfig");
|
||
|
|
$deny =~ s/^\s+//;
|
||
|
|
$deny =~ s/\s+$//;
|
||
|
|
my @denydir = split( /\s*\,\s*/, $deny );
|
||
|
|
foreach $deny (@denydir) {
|
||
|
|
$deny = dir_rel2abs( $docroot, $deny );
|
||
|
|
$deny =~ s!/$!!;
|
||
|
|
$DenyConfig{"$deny/_config.bml"} = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return () if $DenyConfig{$ffile};
|
||
|
|
|
||
|
|
my $conf;
|
||
|
|
if ( file_dontcheck($ffile) && ( $FileConfig{$ffile} || !$FileModTime{$ffile} ) ) {
|
||
|
|
return () unless $FileModTime{$ffile}; # file doesn't exist
|
||
|
|
$conf = $FileConfig{$ffile};
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( !$conf && $file =~ /\.p[lm]$/ ) {
|
||
|
|
return () unless -e $ffile;
|
||
|
|
my $conf = $conf_pl = {};
|
||
|
|
do $ffile;
|
||
|
|
undef $conf_pl;
|
||
|
|
$FileConfig{$ffile} = $conf;
|
||
|
|
return ($ffile);
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ($conf) {
|
||
|
|
unless ( open( C, $ffile ) ) {
|
||
|
|
Apache->log_error("Can't read config file: $file")
|
||
|
|
if -e $file;
|
||
|
|
return ();
|
||
|
|
}
|
||
|
|
|
||
|
|
my $curr_sub;
|
||
|
|
$conf = {};
|
||
|
|
my $sconf = $conf;
|
||
|
|
|
||
|
|
my $save_config = sub {
|
||
|
|
return unless %$sconf;
|
||
|
|
|
||
|
|
# expand $env vars and make paths absolute
|
||
|
|
foreach my $k (qw(LookRoot IncludePath)) {
|
||
|
|
next unless exists $sconf->{$k};
|
||
|
|
$sconf->{$k} =~ s/\$LJHOME/$LJ::HOME/g;
|
||
|
|
$sconf->{$k} =~ s/\$(\w+)/$ENV{$1}/g;
|
||
|
|
$sconf->{$k} = dir_rel2abs( $dirs, $sconf->{$k} );
|
||
|
|
}
|
||
|
|
|
||
|
|
# same as above, but these can be multi-valued, and go into an arrayref
|
||
|
|
foreach my $k (qw(ExtraConfig)) {
|
||
|
|
next unless exists $sconf->{$k};
|
||
|
|
$sconf->{$k} =~ s/\$(\w+)/$1 eq "HTTP_HOST" ? clean_http_host() : $ENV{$1}/eg;
|
||
|
|
$sconf->{$k} = [
|
||
|
|
map { LJ::resolve_file($_) } grep { $_ }
|
||
|
|
split( /\s*,\s*/, $sconf->{$k} )
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
# if child config, copy it to parent config
|
||
|
|
return unless $curr_sub;
|
||
|
|
foreach my $subdir ( split( /\s*,\s*/, $curr_sub ) ) {
|
||
|
|
my $subfile = dir_rel2abs( $dirs, "$subdir/_config.bml" );
|
||
|
|
$conf->{'SubConfig'}->{$subfile} = $sconf;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
while (<C>) {
|
||
|
|
chomp;
|
||
|
|
s/\#.*//;
|
||
|
|
next unless /(\S+)\s+(.+?)\s*$/;
|
||
|
|
my ( $k, $v ) = ( $1, $2 );
|
||
|
|
if ( $k eq "SubConfig:" ) {
|
||
|
|
$save_config->();
|
||
|
|
$curr_sub = $v;
|
||
|
|
$sconf = {%$sconf}; # clone config seen so far. SubConfig inherits those.
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
|
||
|
|
# automatically arrayref-ify certain options
|
||
|
|
$v = [ split( /\s*,\s*/, $v ) ]
|
||
|
|
if $k eq "CookieDomain" && index( $v, ',' ) != -1;
|
||
|
|
|
||
|
|
$sconf->{$k} = $v;
|
||
|
|
}
|
||
|
|
close C;
|
||
|
|
$save_config->();
|
||
|
|
$FileConfig{$ffile} = $conf;
|
||
|
|
}
|
||
|
|
|
||
|
|
my @files = ($ffile);
|
||
|
|
foreach my $cfile ( @{ $conf->{'ExtraConfig'} || [] } ) {
|
||
|
|
unshift @files, load_conffile($cfile);
|
||
|
|
}
|
||
|
|
|
||
|
|
return @files;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub compile {
|
||
|
|
eval $_[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
sub reset_codeblock {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
my BML::Request $req = $Apache::BML::cur_req;
|
||
|
|
my $to_clean = $req->{clean_package};
|
||
|
|
|
||
|
|
no strict;
|
||
|
|
local $^W = 0;
|
||
|
|
my $package = "main::${to_clean}::";
|
||
|
|
*stab = *{"main::"};
|
||
|
|
while ( $package =~ /(\w+?::)/g ) {
|
||
|
|
*stab = ${stab}{$1};
|
||
|
|
}
|
||
|
|
while ( my ( $key, $val ) = each(%stab) ) {
|
||
|
|
return if $DB::signal;
|
||
|
|
deleteglob( $key, $val, undef, $req->{file} );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub deleteglob {
|
||
|
|
no strict;
|
||
|
|
return if $DB::signal;
|
||
|
|
my ( $key, $val, $all, $file ) = @_;
|
||
|
|
local (*entry) = $val;
|
||
|
|
my $fileno;
|
||
|
|
if ( $key !~ /^_</ and defined $entry ) {
|
||
|
|
undef $entry;
|
||
|
|
}
|
||
|
|
if ( $key !~ /^_</ and @entry ) {
|
||
|
|
undef @entry;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (
|
||
|
|
$key eq "ML"
|
||
|
|
|| ( $key ne "main::"
|
||
|
|
&& $key ne "DB::"
|
||
|
|
&& scalar( keys %entry )
|
||
|
|
&& $key !~ /::$/
|
||
|
|
&& $key !~ /^_</
|
||
|
|
&& $val ne "*BML::COOKIE" )
|
||
|
|
)
|
||
|
|
{
|
||
|
|
undef %entry;
|
||
|
|
}
|
||
|
|
if ( defined( $fileno = fileno(*entry) ) ) {
|
||
|
|
|
||
|
|
# do nothing to filehandles?
|
||
|
|
}
|
||
|
|
if ($all) {
|
||
|
|
if ( defined &entry ) {
|
||
|
|
|
||
|
|
# do nothing to subs?
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# $type - "THINGER" in the case of <?thinger Whatever thinger?>
|
||
|
|
# $data - "Whatever" in the case of <?thinger Whatever thinger?>
|
||
|
|
# $option_ref - hash ref to %BMLEnv
|
||
|
|
sub bml_block {
|
||
|
|
my BML::Request $req = shift;
|
||
|
|
my ( $type, $data, $option_ref, $elhash ) = @_;
|
||
|
|
my $realtype = $type;
|
||
|
|
my $previous_block = $req->{'BlockStack'}->[-1];
|
||
|
|
my $env = $req->{'env'};
|
||
|
|
|
||
|
|
# Bail out if we're over 200 frames deep
|
||
|
|
# :TODO: Make the max depth configurable?
|
||
|
|
if ( @{ $req->{BlockStack} } > 200 ) {
|
||
|
|
my $stackSlice = join " -> ", @{ $req->{BlockStack} }[ 0 .. 10 ];
|
||
|
|
return "<b>[Error: Too deep recursion: $stackSlice]</b>";
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( exists $req->{'blockref'}->{"$type/FOLLOW_${previous_block}"} ) {
|
||
|
|
$realtype = "$type/FOLLOW_${previous_block}";
|
||
|
|
}
|
||
|
|
|
||
|
|
my $blockflags = $req->{'blockflags'}->{$realtype};
|
||
|
|
|
||
|
|
# executable perl code blocks
|
||
|
|
if ( $type eq "_CODE" ) {
|
||
|
|
return inline_error("_CODE block failed to execute by permission settings")
|
||
|
|
unless $option_ref->{'DO_CODE'};
|
||
|
|
|
||
|
|
%CodeBlockOpts = ();
|
||
|
|
|
||
|
|
# this will be their package
|
||
|
|
my $md5_package = "BMLCodeBlock::" . Digest::MD5::md5_hex( $req->{'file'} );
|
||
|
|
|
||
|
|
# this will be their handler name
|
||
|
|
my $md5_handler = "handler_" . Digest::MD5::md5_hex($data);
|
||
|
|
|
||
|
|
# we cache code blocks (of templates) also in each *.bml file's
|
||
|
|
# package, since we're too lazy (at the moment) to trace back
|
||
|
|
# each code block to its declaration file.
|
||
|
|
my $unique_key = $md5_package . $md5_handler;
|
||
|
|
|
||
|
|
my $need_compile = !$CodeBlockMade{$unique_key};
|
||
|
|
|
||
|
|
if ($need_compile) {
|
||
|
|
|
||
|
|
# compile (which just calls eval) then check for errors.
|
||
|
|
# we put it off to that sub, historically, to make it
|
||
|
|
# show up separate in profiling, but now we cache
|
||
|
|
# everything, so it pretty much never shows up.
|
||
|
|
compile(
|
||
|
|
join(
|
||
|
|
'',
|
||
|
|
"# line 1 \"$req->{'file'}\"\n",
|
||
|
|
'package ',
|
||
|
|
$md5_package,
|
||
|
|
';',
|
||
|
|
"no strict;",
|
||
|
|
'use vars qw(%ML %COOKIE %POST %GET %FORM);',
|
||
|
|
"*ML = *BML::ML;",
|
||
|
|
"*COOKIE = *BML::COOKIE;",
|
||
|
|
"*GET = *BMLCodeBlock::GET;",
|
||
|
|
"*POST = *BMLCodeBlock::POST;",
|
||
|
|
"*FORM = *BMLCodeBlock::FORM;",
|
||
|
|
$BML::CODE_INIT_PERL, # extra from hook
|
||
|
|
'sub ', $md5_handler, ' {',
|
||
|
|
$data,
|
||
|
|
"\n}"
|
||
|
|
)
|
||
|
|
);
|
||
|
|
|
||
|
|
return handle_code_error( $env, $@ ) if $@;
|
||
|
|
|
||
|
|
$CodeBlockMade{$unique_key} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $cv = \&{"${md5_package}::${md5_handler}"};
|
||
|
|
$req->{clean_package} = $md5_package;
|
||
|
|
my $ret = eval { $cv->( $req, $req->{'scratch'}, $elhash || {} ) };
|
||
|
|
return handle_code_error( $env, $@ ) if $@;
|
||
|
|
|
||
|
|
# don't call bml_decode if BML::noparse() told us not to, there's
|
||
|
|
# no data, or it looks like there are no BML tags
|
||
|
|
return $ret
|
||
|
|
if $CodeBlockOpts{'raw'}
|
||
|
|
or $ret eq ""
|
||
|
|
or ( index( $ret, "<?" ) == -1 && index( $ret, "(=" ) == -1 );
|
||
|
|
|
||
|
|
my $newhtml;
|
||
|
|
bml_decode( $req, \$ret, \$newhtml, {} ); # no opts on purpose: _CODE can't return _CODE
|
||
|
|
return $newhtml;
|
||
|
|
}
|
||
|
|
|
||
|
|
# trim off space from both sides of text data
|
||
|
|
$data =~ s/^\s*(.*?)\s*$/$1/s;
|
||
|
|
|
||
|
|
# load in the properties defined in the data
|
||
|
|
my %element = ();
|
||
|
|
my @elements = ();
|
||
|
|
if ( index( $blockflags, 'F' ) != -1 ) {
|
||
|
|
load_elements( \%element, $data, { 'declorder' => \@elements } );
|
||
|
|
}
|
||
|
|
elsif ( index( $blockflags, 'P' ) != -1 ) {
|
||
|
|
my @itm = split( /\s*\|\s*/, $data );
|
||
|
|
my $ct = 0;
|
||
|
|
foreach (@itm) {
|
||
|
|
$ct++;
|
||
|
|
$element{"DATA$ct"} = $_;
|
||
|
|
push @elements, "DATA$ct";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# single argument block (goes into DATA element)
|
||
|
|
$element{'DATA'} = $data;
|
||
|
|
push @elements, 'DATA';
|
||
|
|
}
|
||
|
|
|
||
|
|
# check built-in block types (those beginning with an underscore)
|
||
|
|
if ( rindex( $type, '_', 0 ) == 0 ) {
|
||
|
|
|
||
|
|
# multi-linguality stuff
|
||
|
|
if ( $type eq "_ML" ) {
|
||
|
|
my $code = $data;
|
||
|
|
return $code
|
||
|
|
if $req->{'lang'} eq 'debug';
|
||
|
|
my $getter = $req->{'env'}->{'HOOK-ml_getter'};
|
||
|
|
return "[ml_getter not defined]" unless $getter;
|
||
|
|
$code = $req->{'r'}->uri . $code
|
||
|
|
if rindex( $code, '.', 0 ) == 0;
|
||
|
|
return $getter->( $req->{'lang'}, $code );
|
||
|
|
}
|
||
|
|
|
||
|
|
# an _INFO block contains special internal information, like which
|
||
|
|
# look files to include
|
||
|
|
if ( $type eq "_INFO" ) {
|
||
|
|
if ( $element{'PACKAGE'} ) { $req->{'package'} = $element{'PACKAGE'}; }
|
||
|
|
if ( $element{'NOCACHE'} ) { $req->{'env'}->{'NoCache'} = 1; }
|
||
|
|
if ( $element{'STATIC'} ) { $req->{'env'}->{'Static'} = 1; }
|
||
|
|
if ( $element{'NOHEADERS'} ) { $req->{'env'}->{'NoHeaders'} = 1; }
|
||
|
|
if ( $element{'NOCONTENT'} ) { $req->{'env'}->{'NoContent'} = 1; }
|
||
|
|
if ( $element{'LOCALBLOCKS'} && $req->{'env'}->{'AllowCode'} ) {
|
||
|
|
my ( %localblock, %localflags );
|
||
|
|
load_elements( \%localblock, $element{'LOCALBLOCKS'} );
|
||
|
|
|
||
|
|
# look for template types
|
||
|
|
foreach my $k ( keys %localblock ) {
|
||
|
|
if ( $localblock{$k} =~ s/^\{([A-Za-z]+)\}// ) {
|
||
|
|
$localflags{$k} = $1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
my @expandconstants;
|
||
|
|
foreach my $k ( keys %localblock ) {
|
||
|
|
$req->{'blockref'}->{$k} = \$localblock{$k};
|
||
|
|
$req->{'blockflags'}->{$k} = $localflags{$k};
|
||
|
|
if ( index( $localflags{$k}, 's' ) != -1 ) { push @expandconstants, $k; }
|
||
|
|
}
|
||
|
|
foreach my $k (@expandconstants) {
|
||
|
|
$localblock{$k} =~
|
||
|
|
s/$TokenOpen([a-zA-Z0-9\_]+?)$TokenClose/${$req->{'blockref'}->{uc($1)} || \""}/og;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $type eq "_INCLUDE" ) {
|
||
|
|
my $code = 0;
|
||
|
|
$code = 1 if ( $element{'CODE'} );
|
||
|
|
foreach my $sec (qw(CODE BML)) {
|
||
|
|
next unless $element{$sec};
|
||
|
|
if ( $req->{'IncludeStack'} && !$req->{'IncludeStack'}->[-1]->{$sec} ) {
|
||
|
|
return inline_error(
|
||
|
|
"Sub-include can't turn on $sec if parent include's $sec was off");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
unless ( $element{'FILE'} =~ /^[a-zA-Z0-9-_\.]{1,255}$/ ) {
|
||
|
|
return inline_error(
|
||
|
|
"Invalid characters in include file name: $element{'FILE'} (code=$code)");
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $req->{'IncludeOpen'}->{ $element{'FILE'} }++ ) {
|
||
|
|
return inline_error("Recursion detected in includes");
|
||
|
|
}
|
||
|
|
push @{ $req->{'IncludeStack'} }, \%element;
|
||
|
|
my $isource = "";
|
||
|
|
my $file = $element{'FILE'};
|
||
|
|
|
||
|
|
# first check if we have a DB-edit hook
|
||
|
|
my $hook = $req->{'env'}->{'HOOK-include_getter'};
|
||
|
|
unless ( $hook && $hook->( $file, \$isource ) ) {
|
||
|
|
$file = $req->{'env'}->{'IncludePath'} . "/" . $file;
|
||
|
|
open( INCFILE, $file ) || return inline_error("Could not open include file.");
|
||
|
|
{ local $/ = undef; $isource = <INCFILE>; }
|
||
|
|
close INCFILE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $element{'BML'} ) {
|
||
|
|
my $newhtml;
|
||
|
|
bml_decode( $req, \$isource, \$newhtml, { DO_CODE => $code } );
|
||
|
|
$isource = $newhtml;
|
||
|
|
}
|
||
|
|
$req->{'IncludeOpen'}->{ $element{'FILE'} }--;
|
||
|
|
pop @{ $req->{'IncludeStack'} };
|
||
|
|
return $isource;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $type eq "_COMMENT" || $type eq "_C" ) {
|
||
|
|
return "";
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $type eq "_EH" ) {
|
||
|
|
return BML::ehtml( $element{'DATA'} );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $type eq "_EB" ) {
|
||
|
|
return BML::ebml( $element{'DATA'} );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $type eq "_EU" ) {
|
||
|
|
return BML::eurl( $element{'DATA'} );
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( $type eq "_EA" ) {
|
||
|
|
return BML::eall( $element{'DATA'} );
|
||
|
|
}
|
||
|
|
|
||
|
|
return inline_error("Unknown core element '$type'");
|
||
|
|
}
|
||
|
|
|
||
|
|
$req->{'BlockStack'}->[-1] = $type;
|
||
|
|
|
||
|
|
# traditional BML Block decoding ... properties of data get inserted
|
||
|
|
# into the look definition; then get BMLitized again
|
||
|
|
return inline_error("Undefined custom element '$type'")
|
||
|
|
unless defined $req->{'blockref'}->{$realtype};
|
||
|
|
|
||
|
|
my $preparsed = ( index( $blockflags, 'p' ) != -1 );
|
||
|
|
|
||
|
|
if ($preparsed) {
|
||
|
|
## does block request pre-parsing of elements?
|
||
|
|
## this is required for blocks with _CODE and AllowCode set to 0
|
||
|
|
foreach my $k (@elements) {
|
||
|
|
my $decoded;
|
||
|
|
bml_decode( $req, \$element{$k}, \$decoded, $option_ref, \%element );
|
||
|
|
$element{$k} = $decoded;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# get the block content to work on; we do this here because it may be a coderef
|
||
|
|
# from BML::register_block() in which case we want to execute it before we try
|
||
|
|
# to run it through the BML parsers
|
||
|
|
my $content = ${ $req->{'blockref'}->{$realtype} };
|
||
|
|
if ( ref $content ) {
|
||
|
|
return inline_error("Unknown type of element '$type'")
|
||
|
|
unless ref $content eq 'CODE';
|
||
|
|
$content = $content->( \%element );
|
||
|
|
return inline_error("Coderef '$type' returned undef/not a string")
|
||
|
|
unless defined $content && !ref $content;
|
||
|
|
}
|
||
|
|
|
||
|
|
# template has no variables or BML tags:
|
||
|
|
return $content if index( $blockflags, 'S' ) != -1;
|
||
|
|
|
||
|
|
my $expanded;
|
||
|
|
if ($preparsed) {
|
||
|
|
$expanded = $content;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$expanded = parsein( $content, \%element );
|
||
|
|
}
|
||
|
|
|
||
|
|
# {R} flag wants variable interpolation, but no expansion:
|
||
|
|
unless ( index( $blockflags, 'R' ) != -1 ) {
|
||
|
|
my $out;
|
||
|
|
push @{ $req->{'BlockStack'} }, "";
|
||
|
|
my $opts = { %{$option_ref} };
|
||
|
|
if ($preparsed) {
|
||
|
|
$opts->{'DO_CODE'} = $req->{'env'}->{'AllowTemplateCode'};
|
||
|
|
}
|
||
|
|
|
||
|
|
unless ( index( $expanded, "<?" ) == -1 && index( $expanded, "(=" ) == -1 ) {
|
||
|
|
bml_decode( $req, \$expanded, \$out, $opts, \%element );
|
||
|
|
$expanded = $out;
|
||
|
|
}
|
||
|
|
|
||
|
|
pop @{ $req->{'BlockStack'} };
|
||
|
|
}
|
||
|
|
|
||
|
|
# t == no final expand, required in tt-runner
|
||
|
|
return $expanded if ( index( $blockflags, 't' ) != -1 );
|
||
|
|
|
||
|
|
$expanded = parsein( $expanded, \%element ) if $preparsed;
|
||
|
|
return $expanded;
|
||
|
|
}
|
||
|
|
|
||
|
|
######## bml_decode
|
||
|
|
#
|
||
|
|
# turns BML source into expanded HTML source
|
||
|
|
#
|
||
|
|
# $inref scalar reference to BML source. $$inref gets destroyed.
|
||
|
|
# $outref scalar reference to where output is appended.
|
||
|
|
# $opts security flags
|
||
|
|
# $elhash optional elements hashref
|
||
|
|
|
||
|
|
use vars qw(%re_decode);
|
||
|
|
|
||
|
|
sub bml_decode {
|
||
|
|
my BML::Request $req = shift;
|
||
|
|
my ( $inref, $outref, $opts, $elhash ) = @_;
|
||
|
|
|
||
|
|
my $block = undef; # what <?block ... block?> are we in?
|
||
|
|
my $data = undef; # what is inside the current block?
|
||
|
|
my $depth = 0; # how many blocks we are deep of the *SAME* type.
|
||
|
|
my $re; # active regular expression for finding closing tag
|
||
|
|
|
||
|
|
pos($$inref) = 0;
|
||
|
|
|
||
|
|
EAT:
|
||
|
|
for ( ; ; ) {
|
||
|
|
|
||
|
|
# currently not in a BML tag... looking for one!
|
||
|
|
if ( !defined $block ) {
|
||
|
|
if (
|
||
|
|
$$inref =~ m/
|
||
|
|
\G # start where last match left off
|
||
|
|
(?> # independent regexp: won't backtrack the .*? below.
|
||
|
|
(.*?) # $1 -> optional non-BML stuff before opening tag
|
||
|
|
$TokenOpen
|
||
|
|
(\w+) # $2 -> tag name
|
||
|
|
)
|
||
|
|
(?: # CASE A: could be 1) immediate tag close, 2) tag close
|
||
|
|
# with data, or 3) slow path, below
|
||
|
|
($TokenClose) | # A.1: $3 -> immediate tag close (depth 0)
|
||
|
|
(?: # A.2: simple close with data (data has no BML start tag of same tag)
|
||
|
|
((?:.(?!$TokenOpen\2\b))+?) # $4 -> one or more chars without following opening BML tags
|
||
|
|
\b\2$TokenClose # matching closing tag
|
||
|
|
) |
|
||
|
|
# A.3: final case: nothing, it's not the fast path. handle below.
|
||
|
|
) # end case A
|
||
|
|
/gcosx
|
||
|
|
)
|
||
|
|
{
|
||
|
|
$$outref .= $1;
|
||
|
|
$block = uc($2);
|
||
|
|
$data = $4 || "";
|
||
|
|
|
||
|
|
# fast path: immediate close or simple data (no opening BML).
|
||
|
|
if ( defined $4 || $3 ) {
|
||
|
|
$$outref .= bml_block( $req, $block, $data, $opts, $elhash );
|
||
|
|
return if $req->{'stop_flag'};
|
||
|
|
$data = undef;
|
||
|
|
$block = undef;
|
||
|
|
next EAT;
|
||
|
|
}
|
||
|
|
|
||
|
|
# slower (nesting) path.
|
||
|
|
# fast path (above) <?foo ...... foo?>
|
||
|
|
# fast: <?foo ... <?bar?> ... foo?>
|
||
|
|
# slow (this path): <?foo ... <?foo?> ... foo?>
|
||
|
|
|
||
|
|
$depth = 1;
|
||
|
|
|
||
|
|
# prepare/find a cached regexp to continue using below
|
||
|
|
# continues below, finding an opening/close of existing tag
|
||
|
|
$re = $re_decode{$block} ||=
|
||
|
|
qr/($TokenClose) | # $1 -> immediate token closing
|
||
|
|
(?:
|
||
|
|
(.+?) # $2 -> non-BML part to push onto $data
|
||
|
|
(?:
|
||
|
|
($TokenOpen$block\b) | # $3 -> increasing depth
|
||
|
|
(\b$block$TokenClose) # $4 -> decreasing depth
|
||
|
|
)
|
||
|
|
)/isx;
|
||
|
|
|
||
|
|
# falls through below.
|
||
|
|
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
# no BML left? append it all and be done.
|
||
|
|
$$outref .= substr( $$inref, pos($$inref) );
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# continue with slow path.
|
||
|
|
|
||
|
|
# the regexp prepared above looks out for these cases: (but not in
|
||
|
|
# this order)
|
||
|
|
#
|
||
|
|
# * Increasing depth:
|
||
|
|
# - some text, then another opening <?foo, increading our depth
|
||
|
|
# (this will always happen somewhere, as this is what defines a slow path)
|
||
|
|
# <?foo bla blah <?foo
|
||
|
|
# * Decreasing depth: (if depth==0, then we're done)
|
||
|
|
# - immediately closing the tag, empty tag
|
||
|
|
# <?foo?>
|
||
|
|
# - closing the tag (if depth == 0, then we're done)
|
||
|
|
# <?foo blah blah foo?>
|
||
|
|
|
||
|
|
if ( $$inref =~ m/\G$re/gc ) {
|
||
|
|
if ($1) {
|
||
|
|
|
||
|
|
# immediate close
|
||
|
|
$depth--;
|
||
|
|
$data .= $1 if $depth; # add closing token if we're still in another tag
|
||
|
|
}
|
||
|
|
elsif ($3) {
|
||
|
|
|
||
|
|
# increasing depth of same block
|
||
|
|
$data .= $2; # data before opening bml tag
|
||
|
|
$data .= $3; # the opening tag itself
|
||
|
|
$depth++;
|
||
|
|
}
|
||
|
|
elsif ($4) {
|
||
|
|
|
||
|
|
# decreasing depth of same block
|
||
|
|
$data .= $2; # data before closing tag
|
||
|
|
$depth--;
|
||
|
|
$data .= $4 if $depth; # add closing tag itself, if we're still in another tag
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$$outref .= inline_error("BML block '$block' has no close");
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
# handle finished blocks
|
||
|
|
if ( $depth == 0 ) {
|
||
|
|
$$outref .= bml_block( $req, $block, $data, $opts, $elhash );
|
||
|
|
return if $req->{'stop_flag'};
|
||
|
|
$data = undef;
|
||
|
|
$block = undef;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# takes a scalar with %%FIELDS%% mixed in and replaces
|
||
|
|
# them with their correct values from an anonymous hash, given
|
||
|
|
# by the second argument to this call
|
||
|
|
sub parsein {
|
||
|
|
my ( $data, $hashref ) = @_;
|
||
|
|
$data =~ s/%%(\w+)%%/$hashref->{uc($1)}/eg;
|
||
|
|
return $data;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub inline_error {
|
||
|
|
return "[Error: <b>@_</b>]";
|
||
|
|
}
|
||
|
|
|
||
|
|
# returns lower-cased, trimmed string
|
||
|
|
sub trim {
|
||
|
|
my $a = $_[0];
|
||
|
|
$a =~ s/^\s*(.*?)\s*$/$1/s;
|
||
|
|
return $a;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub handle_code_error {
|
||
|
|
my ( $env, $msg ) = @_;
|
||
|
|
if ( $env->{'HOOK-codeerror'} ) {
|
||
|
|
my $ret = eval { $env->{'HOOK-codeerror'}->($msg); };
|
||
|
|
return "<b>[Error running codeerror hook]</b>" if $@;
|
||
|
|
return $ret;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
return "<b>[Error: $msg]</b>";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub load_look_perl {
|
||
|
|
my ($file) = @_;
|
||
|
|
|
||
|
|
$conf_pl_look = {};
|
||
|
|
eval { do $file; };
|
||
|
|
if ($@) {
|
||
|
|
print STDERR "Error evaluating BML block conf file $file: $@\n";
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
$LookItems{$file} = $conf_pl_look;
|
||
|
|
undef $conf_pl_look;
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub load_look {
|
||
|
|
my $file = shift;
|
||
|
|
my BML::Request $req = shift; # optional
|
||
|
|
|
||
|
|
my $dontcheck = file_dontcheck($file);
|
||
|
|
if ($dontcheck) {
|
||
|
|
return 0 unless $FileModTime{$file};
|
||
|
|
note_mod_time( $req, $FileModTime{$file} ) if $req;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
note_mod_time( $req, $FileModTime{$file} ) if $req;
|
||
|
|
|
||
|
|
if ( $file =~ /\.pl$/ ) {
|
||
|
|
return load_look_perl($file);
|
||
|
|
}
|
||
|
|
|
||
|
|
my $target = $LookItems{$file} = {};
|
||
|
|
|
||
|
|
foreach my $look ( $file, keys %{ $LookChild{$file} || {} } ) {
|
||
|
|
delete $SchemeData->{$look};
|
||
|
|
delete $SchemeFlags->{$look};
|
||
|
|
}
|
||
|
|
|
||
|
|
open( LOOK, $file );
|
||
|
|
my $look_file;
|
||
|
|
{ local $/ = undef; $look_file = <LOOK>; }
|
||
|
|
close LOOK;
|
||
|
|
load_elements( $target, $look_file );
|
||
|
|
|
||
|
|
# look for template types
|
||
|
|
while ( my ( $k, $v ) = each %$target ) {
|
||
|
|
if ( $v =~ s/^\{([A-Za-z]+)\}// ) {
|
||
|
|
$v = [ $v, $1 ];
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$v = [$v];
|
||
|
|
}
|
||
|
|
$target->{$k} = $v;
|
||
|
|
}
|
||
|
|
|
||
|
|
$LookParent{$file} = undef;
|
||
|
|
if ( $target->{'_PARENT'} ) {
|
||
|
|
my $parfile = file_rel2abs( $file, $target->{'_PARENT'}->[0] );
|
||
|
|
if ( $parfile && load_look($parfile) ) {
|
||
|
|
$LookParent{$file} = $parfile;
|
||
|
|
$LookChild{$parfile}->{$file} = 1;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# given a block of data, loads elements found into
|
||
|
|
sub load_elements {
|
||
|
|
my ( $hashref, $data, $opts ) = @_;
|
||
|
|
my $ol = $opts->{'declorder'};
|
||
|
|
|
||
|
|
my @lines = split( /\r?\n/, $data );
|
||
|
|
|
||
|
|
while (@lines) {
|
||
|
|
my $line = shift @lines;
|
||
|
|
|
||
|
|
# single line declaration:
|
||
|
|
# key=>value
|
||
|
|
if ( $line =~ /^\s*(\w[\w\/]*)=>(.*)/ ) {
|
||
|
|
$hashref->{ uc($1) } = $2;
|
||
|
|
push @$ol, uc($1);
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
|
||
|
|
# multi-line declaration:
|
||
|
|
# key<=
|
||
|
|
# line1
|
||
|
|
# line2
|
||
|
|
# <=key
|
||
|
|
if ( $line =~ /^\s*(\w[\w\/]*)<=\s*$/ ) {
|
||
|
|
my $block = uc($1);
|
||
|
|
my $endblock = qr/^\s*<=$1\s*$/;
|
||
|
|
my $newblock = qr/^\s*$1<=\s*$/;
|
||
|
|
my $depth = 1;
|
||
|
|
my @out;
|
||
|
|
while (@lines) {
|
||
|
|
$line = shift @lines;
|
||
|
|
if ( $line =~ /$newblock/ ) {
|
||
|
|
$depth++;
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
elsif ( $line =~ /$endblock/ ) {
|
||
|
|
$depth--;
|
||
|
|
last unless $depth;
|
||
|
|
}
|
||
|
|
push @out, $line;
|
||
|
|
}
|
||
|
|
if ( $depth == 0 ) {
|
||
|
|
$hashref->{$block} = join( "\n", @out ) . "\n";
|
||
|
|
push @$ol, $block;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
} # end while (@lines)
|
||
|
|
}
|
||
|
|
|
||
|
|
# given a file, checks it's modification time and sees if it's
|
||
|
|
# newer than anything else that compiles into what is the document
|
||
|
|
sub note_file_mod_time {
|
||
|
|
my ( $req, $file ) = @_;
|
||
|
|
note_mod_time( $req, ( stat($file) )[9] );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub note_mod_time {
|
||
|
|
my BML::Request $req = shift;
|
||
|
|
my $mod_time = shift;
|
||
|
|
|
||
|
|
if ($req) {
|
||
|
|
if ( $mod_time > $req->{'most_recent_mod'} ) {
|
||
|
|
$req->{'most_recent_mod'} = $mod_time;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
if ( $mod_time > $Apache::BML::base_recent_mod ) {
|
||
|
|
$Apache::BML::base_recent_mod = $mod_time;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub parse_inputs {
|
||
|
|
|
||
|
|
# only run once
|
||
|
|
# FIXME: ModPerl 2.0: make sure this only runs once or this will be buggy as hell
|
||
|
|
|
||
|
|
# we expect as input a typical request object, we will upgrade it to a proper
|
||
|
|
# request object
|
||
|
|
my $apache_r = Apache2::Request->new(shift);
|
||
|
|
|
||
|
|
# dig out the POST stuff in the new ModPerl 2 way, note that we have to do this
|
||
|
|
# to get multiple parameters in the \0 separated way we expect
|
||
|
|
# Additionally: certain things (editpics.bml, for one) expect %POST to be empty
|
||
|
|
# for multipart POSTs, so don't populate if the content type is 'multipart/form-data'
|
||
|
|
my %posts;
|
||
|
|
unless ( $apache_r->headers_in()->get("Content-Type") =~ m!^multipart/form-data! ) {
|
||
|
|
foreach my $arg ( $apache_r->body ) {
|
||
|
|
$posts{$arg} = join( "\0", $apache_r->body($arg) )
|
||
|
|
if !exists $posts{$arg};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# and now the GET stuff
|
||
|
|
my %gets;
|
||
|
|
foreach my $pair ( split /&/, $apache_r->args ) {
|
||
|
|
my ( $name, $value ) = split /=/, $pair;
|
||
|
|
|
||
|
|
$value =~ tr/+/ /;
|
||
|
|
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||
|
|
|
||
|
|
$name =~ tr/+/ /;
|
||
|
|
$name =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||
|
|
|
||
|
|
$gets{$name} .= $gets{$name} ? "\0$value" : $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
# let BML code blocks see input
|
||
|
|
%BMLCodeBlock::GET = ();
|
||
|
|
%BMLCodeBlock::POST = ();
|
||
|
|
%BMLCodeBlock::FORM = (); # whatever request method is
|
||
|
|
my %input_target = (
|
||
|
|
GET => [ \%BMLCodeBlock::GET ],
|
||
|
|
POST => [ \%BMLCodeBlock::POST ],
|
||
|
|
);
|
||
|
|
push @{ $input_target{ $apache_r->method } }, \%BMLCodeBlock::FORM;
|
||
|
|
foreach my $id ( [ [%gets] => $input_target{'GET'} ], [ [%posts] => $input_target{'POST'} ] ) {
|
||
|
|
while ( my ( $k, $v ) = splice @{ $id->[0] }, 0, 2 ) {
|
||
|
|
foreach my $dest ( @{ $id->[1] } ) {
|
||
|
|
$dest->{$k} .= "\0" if exists $dest->{$k};
|
||
|
|
$dest->{$k} .= $v;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# formatting
|
||
|
|
sub modified_time {
|
||
|
|
my BML::Request $req = shift;
|
||
|
|
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
|
||
|
|
gmtime( $req->{'most_recent_mod'} );
|
||
|
|
my @day = qw{Sun Mon Tue Wed Thu Fri Sat};
|
||
|
|
my @month = qw{Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec};
|
||
|
|
|
||
|
|
if ( $year < 1900 ) { $year += 1900; }
|
||
|
|
|
||
|
|
return sprintf( "$day[$wday], %02d $month[$mon] $year %02d:%02d:%02d GMT",
|
||
|
|
$mday, $hour, $min, $sec );
|
||
|
|
}
|
||
|
|
|
||
|
|
# both Cwd and File::Spec suck. they're portable, but they suck.
|
||
|
|
# these suck too (slow), but they do what i want.
|
||
|
|
|
||
|
|
sub dir_rel2abs {
|
||
|
|
my ( $dir, $rel ) = @_;
|
||
|
|
return $rel if $rel =~ m!^/!;
|
||
|
|
my @dir = grep { $_ ne "" } split( m!/!, $dir );
|
||
|
|
my @rel = grep { $_ ne "" } split( m!/!, $rel );
|
||
|
|
while (@rel) {
|
||
|
|
$_ = shift @rel;
|
||
|
|
next if $_ eq ".";
|
||
|
|
if ( $_ eq ".." ) { pop @dir; next; }
|
||
|
|
push @dir, $_;
|
||
|
|
}
|
||
|
|
return join( '/', '', @dir );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub file_rel2abs {
|
||
|
|
my ( $file, $rel ) = @_;
|
||
|
|
return $rel if $rel =~ m!^/!;
|
||
|
|
$file =~ s!(.+/).*!$1!;
|
||
|
|
return dir_rel2abs( $file, $rel );
|
||
|
|
}
|
||
|
|
|
||
|
|
package BML;
|
||
|
|
|
||
|
|
# returns false if remote browser can't handle the HttpOnly cookie atttribute
|
||
|
|
# (Microsoft extension to make cookies unavailable to scripts)
|
||
|
|
# it renders cookies useless on some browsers. by default, returns true.
|
||
|
|
sub http_only {
|
||
|
|
my $ua = BML::get_client_header("User-Agent");
|
||
|
|
return 0 if $ua =~ /MSIE.+Mac_/;
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub fill_template {
|
||
|
|
my ( $name, $vars ) = @_;
|
||
|
|
die "Can't use BML::fill_template($name) in non-BML context" unless $Apache::BML::cur_req;
|
||
|
|
return Apache::BML::parsein( ${ $Apache::BML::cur_req->{'blockref'}->{ uc($name) } }, $vars );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_scheme {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
return $Apache::BML::cur_req->{'scheme'};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_scheme {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
my BML::Request $req = $Apache::BML::cur_req;
|
||
|
|
my $scheme = shift;
|
||
|
|
return 0 if $scheme =~ /[^\w\-]/;
|
||
|
|
unless ($scheme) {
|
||
|
|
$scheme = $req->{'env'}->{'ForceScheme'}
|
||
|
|
|| DW::SiteScheme->default;
|
||
|
|
}
|
||
|
|
|
||
|
|
my $dw_scheme = DW::SiteScheme->get($scheme);
|
||
|
|
|
||
|
|
if ($dw_scheme) {
|
||
|
|
my $engine = $dw_scheme->engine;
|
||
|
|
if ( $engine eq 'tt' ) {
|
||
|
|
$scheme = 'tt_runner';
|
||
|
|
DW::Request->get->pnote( actual_scheme => $dw_scheme );
|
||
|
|
}
|
||
|
|
elsif ( !$dw_scheme->supports_bml ) {
|
||
|
|
die "Unknown scheme engine $engine for $scheme";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
my $file = "$req->{env}{LookRoot}/$scheme.look";
|
||
|
|
|
||
|
|
return 0 unless Apache::BML::load_look($file);
|
||
|
|
|
||
|
|
$req->{'scheme'} = $scheme;
|
||
|
|
$req->{'scheme_file'} = $file;
|
||
|
|
|
||
|
|
# now we have to combine both of these (along with the VARINIT)
|
||
|
|
# and then expand all the static stuff
|
||
|
|
unless ( exists $SchemeData->{$file} ) {
|
||
|
|
my $iter = $file;
|
||
|
|
my @files;
|
||
|
|
while ($iter) {
|
||
|
|
unshift @files, $iter;
|
||
|
|
$iter = $Apache::BML::LookParent{$iter};
|
||
|
|
}
|
||
|
|
|
||
|
|
my $sd = $SchemeData->{$file} = {};
|
||
|
|
my $sf = $SchemeFlags->{$file} = {};
|
||
|
|
|
||
|
|
foreach my $file (@files) {
|
||
|
|
while ( my ( $k, $v ) = each %{ $Apache::BML::LookItems{$file} } ) {
|
||
|
|
$sd->{$k} = $v->[0];
|
||
|
|
$sf->{$k} = $v->[1];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
foreach my $k ( keys %$sd ) {
|
||
|
|
|
||
|
|
# skip any refs we have, as they aren't processed until run time
|
||
|
|
next if ref $sf->{$k};
|
||
|
|
|
||
|
|
# convert <?imgroot?> into http://www.site.com/img/ etc...
|
||
|
|
next unless index( $sf->{$k}, 's' ) != -1;
|
||
|
|
$sd->{$k} =~ s/$TokenOpen([a-zA-Z0-9\_]+?)$TokenClose/$sd->{uc($1)}/og;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# now, this request needs a copy of (well, references to) the
|
||
|
|
# data above. can't use that directly, since it might
|
||
|
|
# change using _INFO LOCALBLOCKS to declare new file-local blocks
|
||
|
|
$req->{'blockflags'} = {
|
||
|
|
'_INFO' => 'F',
|
||
|
|
'_INCLUDE' => 'F',
|
||
|
|
};
|
||
|
|
$req->{'blockref'} = {};
|
||
|
|
foreach my $k ( keys %{ $SchemeData->{$file} } ) {
|
||
|
|
$req->{'blockflags'}->{$k} = $SchemeFlags->{$file}->{$k};
|
||
|
|
$req->{'blockref'}->{$k} = \$SchemeData->{$file}->{$k};
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_etag {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
my $etag = shift;
|
||
|
|
$Apache::BML::cur_req->{'etag'} = $etag;
|
||
|
|
}
|
||
|
|
|
||
|
|
# when CODE blocks need to look-up static values and such
|
||
|
|
sub get_template_def {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
my $blockname = shift;
|
||
|
|
my $schemefile = $Apache::BML::cur_req->{'scheme_file'};
|
||
|
|
return $SchemeData->{$schemefile}->{ uc($blockname) };
|
||
|
|
}
|
||
|
|
|
||
|
|
sub reset_cookies {
|
||
|
|
%BML::COOKIE_M = ();
|
||
|
|
$BML::COOKIES_PARSED = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_config {
|
||
|
|
my ( $key, $val ) = @_;
|
||
|
|
die "BML::set_config called from non-conffile context.\n" unless $Apache::BML::conf_pl;
|
||
|
|
$Apache::BML::conf_pl->{$key} ||= $val;
|
||
|
|
|
||
|
|
#$Apache::BML::config->{$path}->{$key} = $val;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub noparse {
|
||
|
|
$Apache::BML::CodeBlockOpts{'raw'} = 1;
|
||
|
|
return $_[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
sub decide_language {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
my BML::Request $req = $Apache::BML::cur_req;
|
||
|
|
my $env = $req->{'env'};
|
||
|
|
|
||
|
|
# GET param 'uselang' takes priority
|
||
|
|
my $uselang = $BMLCodeBlock::GET{'uselang'};
|
||
|
|
if ( exists $env->{"Langs-$uselang"} || $uselang eq "debug" ) {
|
||
|
|
return $uselang;
|
||
|
|
}
|
||
|
|
|
||
|
|
# next is their browser's preference
|
||
|
|
my %lang_weight = ();
|
||
|
|
my @langs = split( /\s*,\s*/, lc( $req->{'r'}->headers_in->{"Accept-Language"} ) );
|
||
|
|
my $winner_weight = 0.0;
|
||
|
|
my $winner;
|
||
|
|
foreach (@langs) {
|
||
|
|
|
||
|
|
# do something smarter in future. for now, ditch country code:
|
||
|
|
s/-\w+//;
|
||
|
|
|
||
|
|
if (/(.+);q=(.+)/) {
|
||
|
|
$lang_weight{$1} = $2;
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$lang_weight{$_} = 1.0;
|
||
|
|
}
|
||
|
|
if ( $lang_weight{$_} > $winner_weight && defined $env->{"ISOCode-$_"} ) {
|
||
|
|
$winner_weight = $lang_weight{$_};
|
||
|
|
$winner = $env->{"ISOCode-$_"};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $winner if $winner;
|
||
|
|
|
||
|
|
# next is the default language
|
||
|
|
return $LJ::LANGS[0];
|
||
|
|
|
||
|
|
# lastly, english.
|
||
|
|
return "en";
|
||
|
|
}
|
||
|
|
|
||
|
|
sub register_language {
|
||
|
|
my ($langcode) = @_;
|
||
|
|
die "BML::register_language called from non-conffile context.\n" unless $Apache::BML::conf_pl;
|
||
|
|
$Apache::BML::conf_pl->{"Langs-$langcode"} ||= 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub register_isocode {
|
||
|
|
my ( $isocode, $langcode ) = @_;
|
||
|
|
next unless $isocode =~ /^\w{2,2}$/;
|
||
|
|
die "BML::register_isocode called from non-conffile context.\n" unless $Apache::BML::conf_pl;
|
||
|
|
$Apache::BML::conf_pl->{"ISOCode-$isocode"} ||= $langcode;
|
||
|
|
}
|
||
|
|
|
||
|
|
# get/set the flag to send the Last-Modified header
|
||
|
|
sub want_last_modified {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
$Apache::BML::cur_req->{'want_last_modified'} = $_[0]
|
||
|
|
if defined $_[0];
|
||
|
|
return $Apache::BML::cur_req->{'want_last_modified'};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub note_mod_time {
|
||
|
|
my $mod_time = shift;
|
||
|
|
Apache::BML::note_mod_time( $Apache::BML::cur_req, $mod_time );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub redirect {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
my $url = shift;
|
||
|
|
$Apache::BML::cur_req->{'location'} = $url;
|
||
|
|
finish_suppress_all();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub do_later {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
my $subref = shift;
|
||
|
|
return 0 unless ref $subref eq "CODE";
|
||
|
|
$Apache::BML::cur_req->{'r'}->pool->cleanup_register($subref);
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# $def can be a coderef which will get executed when the template is being
|
||
|
|
# run against a page; otherwise, it's a string
|
||
|
|
sub register_block {
|
||
|
|
my ( $type, $flags, $def ) = @_;
|
||
|
|
my $target = $Apache::BML::conf_pl_look;
|
||
|
|
die "BML::register_block called from non-lookfile context.\n" unless $target;
|
||
|
|
$type = uc($type);
|
||
|
|
|
||
|
|
$target->{$type} = [ $def, $flags ];
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub register_hook {
|
||
|
|
my ( $name, $code ) = @_;
|
||
|
|
die "BML::register_hook called from non-conffile context.\n" unless $Apache::BML::conf_pl;
|
||
|
|
$Apache::BML::conf_pl->{"HOOK-$name"} = $code;
|
||
|
|
}
|
||
|
|
|
||
|
|
# FIXME: these became necessary with ModPerl 2.0, but it would be great if we could
|
||
|
|
# review this change and ensure that this is what we want to be doing here... i.e., if
|
||
|
|
# we haven't defined these yet, then we should define them here? confused.
|
||
|
|
sub get_GET {
|
||
|
|
return \%BMLCodeBlock::GET;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_POST {
|
||
|
|
return \%BMLCodeBlock::POST;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_FORM {
|
||
|
|
return \%BMLCodeBlock::FORM;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_request {
|
||
|
|
|
||
|
|
# we do this, and not use $Apache::BML::r directly because some non-BML
|
||
|
|
# callers sometimes use %BML::COOKIE, so $Apache::BML::r isn't set.
|
||
|
|
# the cookie FETCH below calls this function to try and use BML::get_request(),
|
||
|
|
# else fall back to the global one (for use in profiling/debugging)
|
||
|
|
my $apache_r;
|
||
|
|
eval { $apache_r = Apache2::RequestUtil->request; };
|
||
|
|
$apache_r ||= $Apache::BML::r;
|
||
|
|
return $apache_r;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_query_string {
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
return scalar( $apache_r->args );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_uri {
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
my $uri = $apache_r->uri;
|
||
|
|
$uri =~ s/\.bml$//;
|
||
|
|
return $uri;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_hostname {
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
return $apache_r->hostname;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_method {
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
return $apache_r->method;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_path_info {
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
return $apache_r->path_info;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_remote_ip {
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
return $apache_r->connection()->client_ip;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_remote_host {
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
return $apache_r->connection()->remote_host;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_remote_user {
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
return $apache_r->connection()->user;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_client_header {
|
||
|
|
my $hdr = shift;
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
return $apache_r->headers_in->{$hdr};
|
||
|
|
}
|
||
|
|
|
||
|
|
# <LJFUNC>
|
||
|
|
# class: web
|
||
|
|
# name: BML::self_link
|
||
|
|
# des: Takes the URI of the current page, and adds the current form data
|
||
|
|
# to the URL, then adds any additional data to the URL.
|
||
|
|
# returns: scalar; the full url
|
||
|
|
# args: newvars
|
||
|
|
# des-newvars: A hashref of information to add/override to the link.
|
||
|
|
# </LJFUNC>
|
||
|
|
sub self_link {
|
||
|
|
my $newvars = shift;
|
||
|
|
my $link = $Apache::BML::r->uri;
|
||
|
|
my $form = \%BMLCodeBlock::FORM;
|
||
|
|
|
||
|
|
$link .= "?";
|
||
|
|
foreach ( keys %$newvars ) {
|
||
|
|
if ( !exists $form->{$_} ) { $form->{$_} = ""; }
|
||
|
|
}
|
||
|
|
foreach ( sort keys %$form ) {
|
||
|
|
if ( defined $newvars->{$_} && !$newvars->{$_} ) { next; }
|
||
|
|
my $val = $newvars->{$_} || $form->{$_};
|
||
|
|
next unless $val;
|
||
|
|
$link .= BML::eurl($_) . "=" . BML::eurl($val) . "&";
|
||
|
|
}
|
||
|
|
chop $link;
|
||
|
|
return $link;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub http_response {
|
||
|
|
my ( $code, $msg ) = @_;
|
||
|
|
|
||
|
|
my $apache_r = $Apache::BML::r;
|
||
|
|
$apache_r->status($code);
|
||
|
|
$apache_r->content_type('text/html');
|
||
|
|
$apache_r->print($msg);
|
||
|
|
finish_suppress_all();
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub finish_suppress_all {
|
||
|
|
finish();
|
||
|
|
suppress_headers();
|
||
|
|
suppress_content();
|
||
|
|
}
|
||
|
|
|
||
|
|
sub suppress_headers {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
# set any cookies that we have outstanding
|
||
|
|
send_cookies();
|
||
|
|
$Apache::BML::cur_req->{'env'}->{'NoHeaders'} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub suppress_content {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
$Apache::BML::cur_req->{'env'}->{'NoContent'} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub finish {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
$Apache::BML::cur_req->{'stop_flag'} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_content_type {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
$Apache::BML::cur_req->{'content_type'} = $_[0] if $_[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
# <LJFUNC>
|
||
|
|
# class: web
|
||
|
|
# name: BML::set_status
|
||
|
|
# des: Takes a number to indicate a status (e.g. 404, 403, 410, 500, etc.) and sets
|
||
|
|
# that to be returned to the client when the request finishes.
|
||
|
|
# returns: nothing
|
||
|
|
# args: status
|
||
|
|
# des-newvars: A number representing the status to return to the client.
|
||
|
|
# </LJFUNC>
|
||
|
|
sub set_status {
|
||
|
|
$Apache::BML::r->status( $_[0] + 0 ) if $_[0];
|
||
|
|
}
|
||
|
|
|
||
|
|
sub eall {
|
||
|
|
return ebml( ehtml( $_[0] ) );
|
||
|
|
}
|
||
|
|
|
||
|
|
# escape html
|
||
|
|
sub ehtml {
|
||
|
|
my $a = $_[0];
|
||
|
|
$a =~ s/\&/&/g;
|
||
|
|
$a =~ s/\"/"/g;
|
||
|
|
$a =~ s/\'/&\#39;/g;
|
||
|
|
$a =~ s/</</g;
|
||
|
|
$a =~ s/>/>/g;
|
||
|
|
return $a;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub ebml {
|
||
|
|
my $a = $_[0];
|
||
|
|
my $ra = ref $a ? $a : \$a;
|
||
|
|
$$ra =~ s/\(=(\w)/\(= $1/g; # remove this eventually (old syntax)
|
||
|
|
$$ra =~ s/(\w)=\)/$1 =\)/g; # remove this eventually (old syntax)
|
||
|
|
$$ra =~ s/<\?/<?/g;
|
||
|
|
$$ra =~ s/\?>/?>/g;
|
||
|
|
return if ref $a;
|
||
|
|
return $a;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_language {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
return $Apache::BML::cur_req->{'lang'};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_language_default {
|
||
|
|
return "en" unless Apache::BML::is_initialized();
|
||
|
|
return $Apache::BML::cur_req->{'env'}->{'DefaultLanguage'} || "en";
|
||
|
|
}
|
||
|
|
|
||
|
|
sub get_language_scope {
|
||
|
|
return $BML::ML_SCOPE;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_language_scope {
|
||
|
|
$BML::ML_SCOPE = shift;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub set_language {
|
||
|
|
my ( $lang, $getter ) = @_; # getter is optional
|
||
|
|
my BML::Request $req = $Apache::BML::cur_req;
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
$apache_r->notes->{'langpref'} = $lang;
|
||
|
|
|
||
|
|
# don't rely on $req (the current BML request) being defined, as
|
||
|
|
# we allow callers to use this interface directly from non-BML
|
||
|
|
# requests.
|
||
|
|
if ( Apache::BML::is_initialized() ) {
|
||
|
|
$req->{'lang'} = $lang;
|
||
|
|
$getter ||= $req->{'env'}->{'HOOK-ml_getter'};
|
||
|
|
}
|
||
|
|
|
||
|
|
no strict 'refs';
|
||
|
|
if ( $lang eq "debug" ) {
|
||
|
|
no warnings 'redefine';
|
||
|
|
*{"BML::ml"} = sub {
|
||
|
|
return $_[0];
|
||
|
|
};
|
||
|
|
*{"BML::ML::FETCH"} = sub {
|
||
|
|
return $_[1];
|
||
|
|
};
|
||
|
|
}
|
||
|
|
elsif ($getter) {
|
||
|
|
no warnings 'redefine';
|
||
|
|
*{"BML::ml"} = sub {
|
||
|
|
my ( $code, $vars ) = @_;
|
||
|
|
$code = $BML::ML_SCOPE . $code
|
||
|
|
if rindex( $code, '.', 0 ) == 0;
|
||
|
|
return $getter->( $lang, $code, undef, $vars );
|
||
|
|
};
|
||
|
|
*{"BML::ML::FETCH"} = sub {
|
||
|
|
my $code = $_[1];
|
||
|
|
$code = $BML::ML_SCOPE . $code
|
||
|
|
if rindex( $code, '.', 0 ) == 0;
|
||
|
|
return $getter->( $lang, $code );
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
# multi-lang string
|
||
|
|
# note: sub is changed when BML::set_language is called
|
||
|
|
sub ml {
|
||
|
|
return "[ml_getter not defined]";
|
||
|
|
}
|
||
|
|
|
||
|
|
sub eurl {
|
||
|
|
my $a = $_[0];
|
||
|
|
$a =~ s/([^a-zA-Z0-9_\-.\/\\\: ])/uc sprintf("%%%02x",ord($1))/eg;
|
||
|
|
$a =~ tr/ /+/;
|
||
|
|
return $a;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub durl {
|
||
|
|
my ($a) = @_;
|
||
|
|
$a =~ tr/+/ /;
|
||
|
|
$a =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
|
||
|
|
return $a;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub randlist {
|
||
|
|
my @rlist = @_;
|
||
|
|
my $size = scalar(@rlist);
|
||
|
|
|
||
|
|
my $i;
|
||
|
|
for ( $i = 0 ; $i < $size ; $i++ ) {
|
||
|
|
unshift @rlist, splice( @rlist, $i + int( rand() * ( $size - $i ) ), 1 );
|
||
|
|
}
|
||
|
|
return @rlist;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub page_newurl {
|
||
|
|
my $page = $_[0];
|
||
|
|
my @pair = ();
|
||
|
|
foreach ( sort grep { $_ ne "page" } keys %BMLCodeBlock::FORM ) {
|
||
|
|
push @pair, ( eurl($_) . "=" . eurl( $BMLCodeBlock::FORM{$_} ) );
|
||
|
|
}
|
||
|
|
push @pair, "page=$page";
|
||
|
|
return $Apache::BML::r->uri . "?" . join( "&", @pair );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub paging {
|
||
|
|
my ( $listref, $page, $pagesize ) = @_;
|
||
|
|
$page = 1 unless ( $page && $page eq int($page) );
|
||
|
|
my %self;
|
||
|
|
|
||
|
|
$self{'itemcount'} = scalar( @{$listref} );
|
||
|
|
|
||
|
|
$self{'pages'} = $self{'itemcount'} / $pagesize;
|
||
|
|
$self{'pages'} =
|
||
|
|
$self{'pages'} == int( $self{'pages'} ) ? $self{'pages'} : ( int( $self{'pages'} ) + 1 );
|
||
|
|
|
||
|
|
$page = 1 if $page < 1;
|
||
|
|
$page = $self{'pages'} if $page > $self{'pages'};
|
||
|
|
$self{'page'} = $page;
|
||
|
|
|
||
|
|
$self{'itemfirst'} = $pagesize * ( $page - 1 ) + 1;
|
||
|
|
$self{'itemlast'} = $self{'pages'} == $page ? $self{'itemcount'} : ( $pagesize * $page );
|
||
|
|
|
||
|
|
$self{'items'} = [ @{$listref}[ ( $self{'itemfirst'} - 1 ) .. ( $self{'itemlast'} - 1 ) ] ];
|
||
|
|
|
||
|
|
unless ( $page == 1 ) {
|
||
|
|
$self{'backlink'} = "<a href=\"" . page_newurl( $page - 1 ) . "\"><<<</a>";
|
||
|
|
}
|
||
|
|
unless ( $page == $self{'pages'} ) {
|
||
|
|
$self{'nextlink'} = "<a href=\"" . page_newurl( $page + 1 ) . "\">>>></a>";
|
||
|
|
}
|
||
|
|
|
||
|
|
return %self;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub send_cookies {
|
||
|
|
my $req = shift();
|
||
|
|
|
||
|
|
unless ($req) {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
$req = $Apache::BML::cur_req;
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ( values %{ $req->{'cookies'} } ) {
|
||
|
|
$req->{'r'}->err_headers_out->add( "Set-Cookie" => $_ );
|
||
|
|
}
|
||
|
|
$req->{'cookies'} = {};
|
||
|
|
$req->{'env'}->{'SentCookies'} = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# $expires = 0 to expire when browser closes
|
||
|
|
# $expires = undef to delete cookie
|
||
|
|
sub set_cookie {
|
||
|
|
return undef unless Apache::BML::is_initialized();
|
||
|
|
|
||
|
|
my ( $name, $value, $expires, $path, $domain, $http_only ) = @_;
|
||
|
|
|
||
|
|
my BML::Request $req = $Apache::BML::cur_req;
|
||
|
|
my $e = $req->{'env'};
|
||
|
|
$path = $e->{'CookiePath'} unless defined $path;
|
||
|
|
$domain = $e->{'CookieDomain'} unless defined $domain;
|
||
|
|
|
||
|
|
# let the domain argument be an array ref, so callers can set
|
||
|
|
# cookies in both .foo.com and foo.com, for some broken old browsers.
|
||
|
|
if ( $domain && ref $domain eq "ARRAY" ) {
|
||
|
|
foreach (@$domain) {
|
||
|
|
set_cookie( $name, $value, $expires, $path, $_, $http_only );
|
||
|
|
}
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = gmtime($expires);
|
||
|
|
$year += 1900;
|
||
|
|
|
||
|
|
my @day = qw{Sunday Monday Tuesday Wednesday Thursday Friday Saturday};
|
||
|
|
my @month = qw{Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec};
|
||
|
|
|
||
|
|
my $cookie = eurl($name) . "=" . eurl($value);
|
||
|
|
|
||
|
|
# this logic is confusing potentially
|
||
|
|
unless ( defined $expires && $expires == 0 ) {
|
||
|
|
$cookie .= sprintf( "; expires=$day[$wday], %02d-$month[$mon]-%04d %02d:%02d:%02d GMT",
|
||
|
|
$mday, $year, $hour, $min, $sec );
|
||
|
|
}
|
||
|
|
$cookie .= "; path=$path" if $path;
|
||
|
|
$cookie .= "; domain=$domain" if $domain;
|
||
|
|
$cookie .= "; HttpOnly" if $http_only && BML::http_only();
|
||
|
|
|
||
|
|
# send a cookie directly or cache it for sending later?
|
||
|
|
if ( $e->{'SentCookies'} ) {
|
||
|
|
$req->{'r'}->err_headers_out->add( "Set-Cookie" => $cookie );
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
$req->{'cookies'}->{"$name:$domain"} = $cookie;
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( defined $expires ) {
|
||
|
|
$BML::COOKIE_M{$name} = [$value];
|
||
|
|
}
|
||
|
|
else {
|
||
|
|
delete $BML::COOKIE_M{$name};
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
## Usage:
|
||
|
|
#
|
||
|
|
# BML::decl_params( $field => $rule, .... )
|
||
|
|
#
|
||
|
|
# Rationale: declare all %GET and %POST parameters
|
||
|
|
# you expect, and their types, and you then don't
|
||
|
|
# see unexpected keys or values. Also %FORM is wiped
|
||
|
|
# by using this, since it's old.
|
||
|
|
#
|
||
|
|
# Where:
|
||
|
|
# $field --- %GET/%POST key. or "_default" to match anything else.
|
||
|
|
# $rule --- either a hashref of rule details,
|
||
|
|
# or a type.
|
||
|
|
#
|
||
|
|
# 1) if rule is just a type:
|
||
|
|
# a) named type: "word", "digits", "color"
|
||
|
|
# b) a regular expression object.
|
||
|
|
#
|
||
|
|
# 2) a hashref of keys:
|
||
|
|
# 'type' -- of type of rule from 1) above
|
||
|
|
# 'from' -- either "GET" or "POST" to declare
|
||
|
|
# where this rule applies. you can have
|
||
|
|
# multiple $fields of the same name,
|
||
|
|
# if one is 'from' => GET and one POST.
|
||
|
|
# then their types apply independently.
|
||
|
|
#
|
||
|
|
# Example:
|
||
|
|
# BML::decl_params(
|
||
|
|
# count => "digits",
|
||
|
|
# sym => "word",
|
||
|
|
# onecap => qr/^[A-Z]$/,
|
||
|
|
# postdata => {
|
||
|
|
# from => 'POST',
|
||
|
|
# },
|
||
|
|
# );
|
||
|
|
#
|
||
|
|
|
||
|
|
sub decl_params {
|
||
|
|
my %rules; # {GET|POST|ANY}-"field" => { type => ..., }
|
||
|
|
while (@_) {
|
||
|
|
my $sym = shift;
|
||
|
|
my $rule = shift;
|
||
|
|
unless ( ref $rule eq "HASH" ) {
|
||
|
|
$rule = { type => $rule, };
|
||
|
|
}
|
||
|
|
$rule->{from} ||= "ANY";
|
||
|
|
|
||
|
|
# convert named types to regexps
|
||
|
|
my $types = {
|
||
|
|
'digits' => qr/^\d+$/,
|
||
|
|
'word' => qr/^\w+$/,
|
||
|
|
'color' => qr/^\#[0-9a-f]{3,6}$/i,
|
||
|
|
};
|
||
|
|
if ( $types->{ $rule->{type} } ) {
|
||
|
|
$rule->{type} = $types->{ $rule->{type} };
|
||
|
|
}
|
||
|
|
$rules{"$rule->{from}-$sym"} = $rule;
|
||
|
|
}
|
||
|
|
|
||
|
|
# if they declared their parameters, they get potentially
|
||
|
|
# unsafe ones back, which we might've otherwise hidden
|
||
|
|
# out of paranoia:
|
||
|
|
while ( my ( $k, $v ) = each %BMLCodeBlock::GET_POTENTIAL_XSS ) {
|
||
|
|
$BMLCodeBlock::GET{$k} = $v;
|
||
|
|
}
|
||
|
|
|
||
|
|
# using this destroys %FORM. it's deprecated anyway.
|
||
|
|
%BMLCodeBlock::FORM = ();
|
||
|
|
my %to_clean = (
|
||
|
|
GET => \%BMLCodeBlock::GET,
|
||
|
|
POST => \%BMLCodeBlock::POST,
|
||
|
|
);
|
||
|
|
foreach my $what ( keys %to_clean ) {
|
||
|
|
my $hash = $to_clean{$what};
|
||
|
|
foreach my $k ( keys %$hash ) {
|
||
|
|
my $rule =
|
||
|
|
$rules{"$what-$k"}
|
||
|
|
|| $rules{"ANY-$k"}
|
||
|
|
|| $rules{"$what-_default"}
|
||
|
|
|| $rules{"ANY-_default"};
|
||
|
|
unless ($rule) {
|
||
|
|
delete $hash->{$k};
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
my $rx = $rule->{type};
|
||
|
|
if ( $rx && $hash->{$k} !~ /$rx/ ) {
|
||
|
|
delete $hash->{$k};
|
||
|
|
next;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# cookie support
|
||
|
|
package BML::Cookie;
|
||
|
|
|
||
|
|
sub TIEHASH {
|
||
|
|
my $class = shift;
|
||
|
|
my $self = {};
|
||
|
|
bless $self;
|
||
|
|
return $self;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub FETCH {
|
||
|
|
my ( $t, $key ) = @_;
|
||
|
|
|
||
|
|
# we do this, and not use $Apache::BML::r directly because some non-BML
|
||
|
|
# callers sometimes use %BML::COOKIE.
|
||
|
|
my $apache_r = BML::get_request();
|
||
|
|
unless ($BML::COOKIES_PARSED) {
|
||
|
|
foreach ( split( /;\s+/, $apache_r->headers_in->{"Cookie"} ) ) {
|
||
|
|
next unless ( $_ =~ /(.*)=(.*)/ );
|
||
|
|
my ( $name, $value ) = ( $1, $2 );
|
||
|
|
my $dname = BML::durl($name);
|
||
|
|
my $dvalue = BML::durl($value);
|
||
|
|
push @{ $BML::COOKIE_M{$dname} ||= [] }, $dvalue;
|
||
|
|
}
|
||
|
|
$BML::COOKIES_PARSED = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
# return scalar value, or arrayref if key has [] appende
|
||
|
|
return $BML::COOKIE_M{$key} || [] if $key =~ s/\[\]$//;
|
||
|
|
return ( $BML::COOKIE_M{$key} || [] )->[-1];
|
||
|
|
}
|
||
|
|
|
||
|
|
sub STORE {
|
||
|
|
my ( $t, $key, $val ) = @_;
|
||
|
|
my $etime = 0;
|
||
|
|
my $http_only = 0;
|
||
|
|
( $val, $etime, $http_only ) = @$val if ref $val eq "ARRAY";
|
||
|
|
$etime = undef unless $val ne "";
|
||
|
|
BML::set_cookie( $key, $val, $etime, undef, undef, $http_only );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub DELETE {
|
||
|
|
my ( $t, $key ) = @_;
|
||
|
|
STORE( $t, $key, undef );
|
||
|
|
}
|
||
|
|
|
||
|
|
sub CLEAR {
|
||
|
|
my ($t) = @_;
|
||
|
|
foreach ( keys %BML::COOKIE_M ) {
|
||
|
|
STORE( $t, $_, undef );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
sub EXISTS {
|
||
|
|
my ( $t, $key ) = @_;
|
||
|
|
return defined $BML::COOKIE_M{$key};
|
||
|
|
}
|
||
|
|
|
||
|
|
sub FIRSTKEY {
|
||
|
|
my ($t) = @_;
|
||
|
|
keys %BML::COOKIE_M;
|
||
|
|
return each %BML::COOKIE_M;
|
||
|
|
}
|
||
|
|
|
||
|
|
sub NEXTKEY {
|
||
|
|
my ( $t, $key ) = @_;
|
||
|
|
return each %BML::COOKIE_M;
|
||
|
|
}
|
||
|
|
|
||
|
|
# provide %BML::ML & %BMLCodeBlock::ML support:
|
||
|
|
package BML::ML;
|
||
|
|
|
||
|
|
sub TIEHASH {
|
||
|
|
my $class = shift;
|
||
|
|
my $self = {};
|
||
|
|
bless $self;
|
||
|
|
return $self;
|
||
|
|
}
|
||
|
|
|
||
|
|
# note: sub is changed when BML::set_language is called.
|
||
|
|
sub FETCH {
|
||
|
|
return "[ml_getter not defined]";
|
||
|
|
}
|
||
|
|
|
||
|
|
# do nothing
|
||
|
|
sub CLEAR { }
|
||
|
|
|
||
|
|
1;
|
||
|
|
|
||
|
|
# Local Variables:
|
||
|
|
# mode: perl
|
||
|
|
# c-basic-indent: 4
|
||
|
|
# indent-tabs-mode: nil
|
||
|
|
# End:
|
||
|
|
|