mourningdove/cgi-bin/ljlib.pl
2026-05-24 01:03:05 +00:00

994 lines
30 KiB
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.
package LJ;
use strict;
no warnings 'uninitialized';
BEGIN {
# ugly hack to shutup dependent libraries which sometimes want to bring in
# ljlib.pl (via require, ick!). so this lets them know if it's recursive.
# we REALLY need to move the rest of this crap to .pm files.
# ensure we have $LJ::HOME, or complain very vigorously
$LJ::HOME ||= $ENV{LJHOME};
die "No \$LJ::HOME set, or not a directory!\n"
unless $LJ::HOME && -d $LJ::HOME;
# Allow setting dev server mode from environment. This is needed because
# Plack startup doesn't go through Apache config where $IS_DEV_SERVER is
# normally set. WARNING: Must NEVER be set in production — it enables
# ?as= user impersonation, auto-verified accounts, and skips domain logic.
$LJ::IS_DEV_SERVER = 1 if $ENV{LJ_IS_DEV_SERVER};
use lib ( $LJ::HOME || $ENV{LJHOME} ) . "/extlib/lib/perl5";
# Please do not change this to "LJ::Directories"
require $LJ::HOME . "/cgi-bin/LJ/Directories.pm";
}
# now that the library is setup, we can start pulling things in. start with
# the configuration library we need.
use LJ::Config;
BEGIN {
# mod_perl does this early too, make sure we do as well
LJ::Config->load;
$LJ::LOGMEMCFMT = 'NNNQN';
$LJ::PUBLICBIT = 2**63;
}
# Now set up logging support for everybody else to access; this is done
# very early. We may be called by a test though, which will set the flag,
# and in that case we disable all the logging.
use Log::Log4perl;
BEGIN {
if ($LJ::_T_CONFIG) {
# Tests, don't log
my $conf = q{
log4perl.rootLogger=FATAL, DevNull
log4perl.appender.DevNull=Log::Log4perl::Appender::File
log4perl.appender.DevNull.filename=/dev/null
log4perl.appender.DevNull.layout=Log::Log4perl::Layout::SimpleLayout
};
Log::Log4perl::init( \$conf );
}
else {
Log::Log4perl::init_and_watch( LJ::resolve_file('etc/log4perl.conf'), 10 );
}
}
use Carp;
use DBI;
use DBI::Role;
use HTTP::Date ();
use LJ::Utils qw(rand_chars);
use LJ::Hooks;
use LJ::MemCache;
use LJ::Error;
use LJ::Auth; # has a bunch of pkg LJ functions at bottom
use LJ::User; # has a bunch of pkg LJ, non-OO methods at bottom
use LJ::Entry; # has a bunch of pkg LJ, non-OO methods at bottom
use LJ::Global::Constants; # formerly LJ::Constants
use Time::Local ();
use Storable ();
use Compress::Zlib ();
use DW::Request;
use TheSchwartz;
use TheSchwartz::Job;
use LJ::Comment;
use LJ::Message;
use LJ::ConvUTF8;
use LJ::Userpic;
use LJ::ModuleCheck;
use IO::Socket::INET;
use IO::Socket::SSL;
use Mozilla::CA;
use LJ::UniqCookie;
use LJ::WorkerResultStorage;
use DW::External::Account;
use DW::External::User;
use DW::Logic::LogItems;
use LJ::CleanHTML;
use DW::LatestFeed;
use LJ::Keywords;
use LJ::DB;
use LJ::Tags;
use LJ::TextUtil;
use LJ::Time;
use LJ::Capabilities;
use DW::Mood;
use LJ::Global::Img; # defines LJ::Img
use LJ::Global::Secrets; # defines LJ::Secrets
use DW::Media;
use DW::Stats;
use DW::Proxy;
use DW::TaskQueue;
use DW::BlobStore;
# Load more modules so that we get as much advantage out of prefork
# memory allocation as possible (as well as moving as much loading cost
# to startup time)
BEGIN {
# Do not run if we're in a test
unless ($LJ::_T_CONFIG) {
LJ::ModuleCheck->have_xmlatom;
LJ::Hooks::_load_hooks_dir();
Storable::thaw( Storable::freeze( {} ) );
foreach my $minifile ( "GIF89a", "\x89PNG\x0d\x0a\x1a\x0a", "\xFF\xD8" ) {
Image::Size::imgsize( \$minifile );
}
LJ::CleanHTML::helper_preload();
# load drivers depending on what we have available
eval "use DBD::mysql;";
unless ($@) {
DBI->install_driver("mysql");
}
}
}
$Net::HTTPS::SSL_SOCKET_CLASS = "IO::Socket::SSL";
# make Unicode::MapUTF8 autoload:
sub Unicode::MapUTF8::AUTOLOAD {
die "Unknown subroutine $Unicode::MapUTF8::AUTOLOAD"
unless $Unicode::MapUTF8::AUTOLOAD =~ /::(utf8_supported_charset|to_utf8|from_utf8)$/;
LJ::ConvUTF8->load;
no strict 'refs';
goto *{$Unicode::MapUTF8::AUTOLOAD}{CODE};
}
sub END { LJ::end_request(); }
require "$LJ::HOME/cgi-bin/ljlib-local.pl"
if -e "$LJ::HOME/cgi-bin/ljlib-local.pl";
# if this is a dev server, alias LJ::D to Data::Dumper::Dumper
if ($LJ::IS_DEV_SERVER) {
eval "use Data::Dumper ();";
*LJ::D = \&Data::Dumper::Dumper;
}
LJ::MemCache::init();
# $LJ::PROTOCOL_VER is the version of the client-server protocol
# used uniformly by server code which uses the protocol. We used
# to set this to "0" if $LJ::UNICODE was false, but now we assume
# we always want to use Unicode.
$LJ::PROTOCOL_VER = "1";
# declare views for user journals
%LJ::viewinfo = (
"lastn" => {
"des" => "Most Recent Events",
},
"archive" => {
"des" => "Archive",
},
"day" => {
"des" => "Day View",
},
"read" => {
"des" => "Reading Page",
"owner_props" => [ "opt_usesharedpic", "friendspagetitle", "friendspagesubtitle" ],
},
"network" => {
"des" => "Network View",
"styleof" => "read",
},
"data" => {
"des" => "Data View (RSS, etc.)",
"owner_props" => [ "opt_whatemailshow", "no_mail_alias" ],
},
"rss" => { # this is now provided by the "data" view.
"des" => "RSS View (XML)",
},
"res" => {
"des" => "S2-specific resources (stylesheet)",
},
"info" => {
# just a redirect to profile.bml for now.
# in S2, will be a real view.
"des" => "Profile Page",
},
"profile" => {
# just a redirect to profile.bml for now.
# in S2, will be a real view.
"des" => "Profile Page",
},
"tag" => {
"des" => "Filtered Recent Entries View",
},
"security" => {
"des" => "Filtered Recent Entries View",
},
"update" => {
# just a redirect to update.bml for now.
# real solution is some sort of better nav
# within journal styles.
"des" => "Update Journal",
},
"icons" => {
"des" => "Icons",
},
);
## we want to set this right away, so when we get a HUP signal later
## and our signal handler sets it to true, perl doesn't need to malloc,
## since malloc may not be thread-safe and we could core dump.
## see LJ::clear_caches and LJ::handle_caches
$LJ::CLEAR_CACHES = 0;
my $GTop;
my %SecretCache;
## if this library is used in a BML page, we don't want to destroy BML's
## HUP signal handler.
if ( $SIG{'HUP'} ) {
my $oldsig = $SIG{'HUP'};
$SIG{'HUP'} = sub {
&{$oldsig} if ref $oldsig eq "CODE";
LJ::clear_caches();
};
}
else {
$SIG{'HUP'} = \&LJ::clear_caches;
}
# Initialize our statistics reporting library if needed
if ( $LJ::STATS{host} && $LJ::STATS{port} ) {
DW::Stats::setup( $LJ::STATS{host}, $LJ::STATS{port} );
}
sub locker {
return $LJ::LOCKER_OBJ if $LJ::LOCKER_OBJ;
eval "use DDLockClient ();";
die "Couldn't load locker client: $@" if $@;
$LJ::LOCKER_OBJ = new DDLockClient(
servers => [@LJ::LOCK_SERVERS],
lockdir => $LJ::LOCKDIR || "$LJ::HOME/locks",
);
return $LJ::LOCKER_OBJ;
}
sub gearman_client {
my $purpose = shift;
return undef unless @LJ::GEARMAN_SERVERS;
eval "use Gearman::Client; 1;" or die "No Gearman::Client available: $@";
my $client = Gearman::Client->new;
$client->job_servers(@LJ::GEARMAN_SERVERS);
return $client;
}
sub theschwartz {
return LJ::Test->theschwartz(@_) if $LJ::_T_FAKESCHWARTZ;
my $opts = shift;
my $role = $opts->{role} || "default";
return $LJ::SchwartzClient{$role} if $LJ::SchwartzClient{$role};
unless ( scalar grep { defined $_->{role} } @LJ::THESCHWARTZ_DBS ) { # old config
$LJ::SchwartzClient{$role} = TheSchwartz->new( databases => \@LJ::THESCHWARTZ_DBS );
return $LJ::SchwartzClient{$role};
}
my @dbs = grep { $_->{role}->{$role} } @LJ::THESCHWARTZ_DBS;
die "Unknown role in LJ::theschwartz: '$role'" unless @dbs;
$LJ::SchwartzClient{$role} = TheSchwartz->new( databases => \@dbs );
return $LJ::SchwartzClient{$role};
}
sub gtop {
unless ($LJ::GTOP_LOADED) {
eval "use GTop;";
die "Couldn't load GTop: $@" if $@;
$LJ::GTOP_LOADED = 1;
}
return $GTop ||= GTop->new;
}
# Loads and caches one or more of the various *proplist (and ratelist)
# tables, which describe the various meta-data that can be stored on log
# (journal) items, comments, users, media, etc.
#
# Please use LJ::get_prop to actually retrieve properties. You probably
# don't want to call this function directly.
sub load_props {
my %keyname = (
log => [ 'propid', 'logproplist' ],
media => [ 'propid', 'media_prop_list' ],
rate => [ 'rlid', 'ratelist' ],
talk => [ 'tpropid', 'talkproplist' ],
user => [ 'upropid', 'userproplist' ],
);
my $dbr = LJ::get_db_reader()
or croak 'Failed to get database reader handle';
foreach my $t (@_) {
confess 'Attempted to load invalid property list'
unless exists $keyname{$t};
next if defined $LJ::CACHE_PROP{$t};
my ( $key, $table ) = @{ $keyname{$t} };
my $res = $dbr->selectall_hashref( "SELECT * FROM $table", $key );
croak $dbr->errstr if $dbr->err;
croak 'Failed to load properties from list'
unless $res && ref $res eq 'HASH';
foreach my $id ( keys %$res ) {
my $p = $res->{$id};
$p->{id} = $id;
$LJ::CACHE_PROP{$t}->{ $p->{name} } = $p;
$LJ::CACHE_PROPID{$t}->{ $p->{id} } = $p;
}
}
}
# <LJFUNC>
# name: LJ::get_prop
# des: This is used to retrieve
# a hashref of a row from the given tablename's proplist table.
# One difference from getting it straight from the database is
# that the 'id' key is always present, as a copy of the real
# proplist unique id for that table.
# args: table, name
# returns: hashref of proplist row from db
# des-table: the tables to get a proplist hashref from. Can be one of
# "log", "talk", or "user".
# des-name: the name of the prop to get the hashref of.
# </LJFUNC>
sub get_prop {
my $table = shift;
my $name = shift;
unless ( defined $LJ::CACHE_PROP{$table} && $LJ::CACHE_PROP{$table}->{$name} ) {
$LJ::CACHE_PROP{$table} = undef;
LJ::load_props($table);
}
unless ( $LJ::CACHE_PROP{$table} ) {
warn "Prop table has no data: $table" if $LJ::IS_DEV_SERVER;
return undef;
}
unless ( $LJ::CACHE_PROP{$table}->{$name} ) {
warn "Prop does not exist: $table - $name" if $LJ::IS_DEV_SERVER;
return undef;
}
return $LJ::CACHE_PROP{$table}->{$name};
}
# <LJFUNC>
# name: LJ::load_codes
# des: Populates hashrefs with lookup data from the database or from memory,
# if already loaded in the past. Examples of such lookup data include
# state codes, color name/value mappings, etc.
# args: dbarg?, whatwhere
# des-whatwhere: a hashref with keys being the code types you want to load
# and their associated values being hashrefs to where you
# want that data to be populated.
# </LJFUNC>
sub load_codes {
my $req = shift;
my $dbr = LJ::get_db_reader()
or die "Unable to get database handle";
foreach my $type ( keys %{$req} ) {
my $memkey = "load_codes:$type";
unless ( $LJ::CACHE_CODES{$type} ||= LJ::MemCache::get($memkey) ) {
$LJ::CACHE_CODES{$type} = [];
my $sth = $dbr->prepare("SELECT code, item, sortorder FROM codes WHERE type=?");
$sth->execute($type);
while ( my ( $code, $item, $sortorder ) = $sth->fetchrow_array ) {
push @{ $LJ::CACHE_CODES{$type} }, [ $code, $item, $sortorder ];
}
@{ $LJ::CACHE_CODES{$type} } =
sort { $a->[2] <=> $b->[2] } @{ $LJ::CACHE_CODES{$type} };
LJ::MemCache::set( $memkey, $LJ::CACHE_CODES{$type}, 60 * 15 );
}
foreach my $it ( @{ $LJ::CACHE_CODES{$type} } ) {
if ( ref $req->{$type} eq "HASH" ) {
$req->{$type}->{ $it->[0] } = $it->[1];
}
elsif ( ref $req->{$type} eq "ARRAY" ) {
push @{ $req->{$type} }, { 'code' => $it->[0], 'item' => $it->[1] };
}
}
}
}
# <LJFUNC>
# name: LJ::clear_caches
# des: This function is called from a HUP signal handler and is intentionally
# very very simple (1 line) so we don't core dump on a system without
# reentrant libraries. It just sets a flag to clear the caches at the
# beginning of the next request (see [func[LJ::handle_caches]]).
# There should be no need to ever call this function directly.
# </LJFUNC>
sub clear_caches {
$LJ::CLEAR_CACHES = 1;
}
# <LJFUNC>
# name: LJ::handle_caches
# des: clears caches if the CLEAR_CACHES flag is set from an earlier
# HUP signal that called [func[LJ::clear_caches]], otherwise
# does nothing.
# returns: true (always) so you can use it in a conjunction of
# statements in a while loop around the application like:
# while (LJ::handle_caches() && FCGI::accept())
# </LJFUNC>
sub handle_caches {
return 1 unless $LJ::CLEAR_CACHES;
$LJ::CLEAR_CACHES = 0;
LJ::Config->load;
$LJ::DBIRole->flush_cache();
%LJ::CACHE_PROP = ();
%LJ::CACHE_STYLE = ();
$LJ::CACHED_MOODS = 0;
$LJ::CACHED_MOOD_MAX = 0;
%LJ::CACHE_MOODS = ();
%LJ::CACHE_MOOD_THEME = ();
%LJ::CACHE_USERID = ();
%LJ::CACHE_USERNAME = ();
%LJ::CACHE_CODES = ();
%LJ::CACHE_USERPROP = (); # {$prop}->{ 'upropid' => ... , 'indexed' => 0|1 };
%LJ::CACHE_ENCODINGS = ();
return 1;
}
# <LJFUNC>
# name: LJ::start_request
# des: Before a new web request is obtained, this should be called to
# determine if process should die or keep working, clean caches,
# reload config files, etc.
# returns: 1 if a new request is to be processed, 0 if process should die.
# </LJFUNC>
sub start_request {
handle_caches();
# TODO: check process growth size
# clear per-request caches
LJ::unset_remote(); # clear cached remote
$LJ::ACTIVE_JOURNAL = undef; # for LJ::{get,set}_active_journal
%LJ::CACHE_USERPIC = (); # picid -> hashref
%LJ::CACHE_USERPIC_INFO = (); # uid -> { ... }
%LJ::CACHE_S2THEME = ();
%LJ::REQ_CACHE_USER_NAME = (); # users by name
%LJ::REQ_CACHE_USER_ID = (); # users by id
%LJ::REQ_CACHE_REL = (); # relations from LJ::check_rel()
%LJ::REQ_LANGDATFILE = (); # caches language files
%LJ::S2::REQ_CACHE_STYLE_ID = (); # styleid -> hashref of s2 layers for style
%LJ::S2::REQ_CACHE_LAYER_ID =
(); # layerid -> hashref of s2 layer info (from LJ::S2::load_layer)
%LJ::S2::REQ_CACHE_LAYER_INFO =
(); # layerid -> hashref of s2 layer info (from LJ::S2::load_layer_info)
%LJ::REQ_HEAD_HAS = (); # avoid code duplication for js
%LJ::NEEDED_RES = (); # needed resources (css/js/etc):
@LJ::NEEDED_RES = (); # needed resources, in order requested (implicit dependencies)
# keys are relative from htdocs, values 1 or 2 (1=external, 2=inline)
%LJ::REQ_GLOBAL = (); # per-request globals
%LJ::_ML_USED_STRINGS = (); # strings looked up in this web request
%LJ::REQ_CACHE_USERTAGS =
(); # uid -> { ... }; populated by get_usertags, so we don't load it twice
$LJ::ACTIVE_RES_GROUP = undef; # use whatever is current site default
%LJ::PAID_STATUS = (); # per-request paid status
%LJ::REQUEST_CACHE = (); # request cached items ( longterm goal, store everything in here )
$LJ::CACHE_REMOTE_BOUNCE_URL = undef;
LJ::Userpic->reset_singletons;
LJ::Comment->reset_singletons;
LJ::Entry->reset_singletons;
LJ::Message->reset_singletons;
LJ::UniqCookie->clear_request_cache;
# clear the handle request cache (like normal cache, but verified already for
# this request to be ->ping'able).
$LJ::DBIRole->clear_req_cache();
# need to suck db weights down on every request (we check
# the serial number of last db weight change on every request
# to validate master db connection, instead of selecting
# the connection ID... just as fast, but with a point!)
$LJ::DBIRole->trigger_weight_reload();
# reset BML's cookies
eval { BML::reset_cookies() };
# reload config if necessary
LJ::Config->start_request_reload;
# reset the request abstraction layer
DW::Request->reset;
# include standard files if this is web-context
LJ::register_standard_resources();
LJ::Hooks::run_hooks("start_request");
return 1;
}
# Register standard site-wide CSS/JS resources. Called from start_request
# (for Apache, where DW::Request is already available) and from the Plack
# middleware (where the request must be created before this can run).
sub register_standard_resources {
my $r = DW::Request->get or return;
# sorry everybody, this is a gross hack ... we need to not use jquery on the shop since
# jquery is pretty old and crufty and PCI compliance etc, so we're just not going to include
# it here if we're on that domain
my $NO_JQUERY = 0;
if ( $LJ::DOMAIN_SHOP
&& $LJ::DOMAIN_SHOP ne $LJ::DOMAIN_WEB
&& $r->host eq $LJ::DOMAIN_SHOP )
{
$NO_JQUERY = 1;
}
# start with jquery core unless we've disabled it
LJ::need_res( { group => 'foundation', priority => $LJ::LIB_RES_PRIORITY },
'js/jquery/jquery-1.8.3.js' )
unless $NO_JQUERY;
# note that we're calling need_res and advising that these items
# are the new style global items
LJ::need_res(
{ group => 'foundation', priority => $LJ::LIB_RES_PRIORITY },
'js/foundation/vendor/custom.modernizr.js',
'js/foundation/foundation/foundation.js',
'js/foundation/foundation/foundation.topbar.js',
'js/dw/dw-core.js'
);
LJ::need_res(
{ group => 'jquery', priority => $LJ::LIB_RES_PRIORITY },
# jquery library is the big one, load first
'js/jquery/jquery-1.8.3.js',
# the rest of the libraries
qw(
js/dw/dw-core.js
),
);
# old/standard libraries are below here.
# standard site-wide JS and CSS
LJ::need_res(
{ priority => $LJ::LIB_RES_PRIORITY }, qw(
js/6alib/core.js
js/6alib/dom.js
js/6alib/httpreq.js
js/livejournal.js
)
);
LJ::need_res(
{ priority => $LJ::LIB_RES_PRIORITY, group => "all" }, qw (
stc/lj_base.css
)
);
# esn ajax
LJ::need_res(
{ priority => $LJ::LIB_RES_PRIORITY }, qw(
js/esn.js
stc/esn.css
)
) if LJ::is_enabled('esn_ajax');
# contextual popup JS
LJ::need_res(
{ priority => $LJ::LIB_RES_PRIORITY, group => "default" }, qw(
js/6alib/ippu.js
js/lj_ippu.js
js/6alib/hourglass.js
js/contextualhover.js
stc/contextualhover.css
)
);
my @ctx_popup_libraries = qw(
js/jquery/jquery.ui.core.js
js/jquery/jquery.ui.widget.js
js/jquery/jquery.ui.tooltip.js
js/jquery.ajaxtip.js
js/jquery/jquery.ui.position.js
stc/jquery/jquery.ui.core.css
stc/jquery/jquery.ui.tooltip.css
js/jquery.hoverIntent.js
js/jquery.contextualhover.js
stc/jquery.contextualhover.css
);
LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY, group => 'jquery' }, @ctx_popup_libraries );
# foundation only gets this sometimes
LJ::need_res( { priority => $LJ::LIB_RES_PRIORITY, group => 'foundation' },
@ctx_popup_libraries )
unless $NO_JQUERY;
# development JS
LJ::need_res(
{ priority => $LJ::LIB_RES_PRIORITY }, qw(
js/6alib/devel.js
)
) if $LJ::IS_DEV_SERVER;
}
# <LJFUNC>
# name: LJ::end_request
# des: Clears cached DB handles (if [ljconfig[disconnect_dbs]] is
# true), and disconnects memcached handles (if [ljconfig[disconnect_memcache]] is
# true).
# </LJFUNC>
sub end_request {
LJ::flush_cleanup_handlers();
LJ::DB::disconnect_dbs() if $LJ::DISCONNECT_DBS;
LJ::MemCache::disconnect_all() if $LJ::DISCONNECT_MEMCACHE;
return 1;
}
# <LJFUNC>
# name: LJ::flush_cleanup_handlers
# des: Runs all cleanup handlers registered in @LJ::CLEANUP_HANDLERS
# </LJFUNC>
sub flush_cleanup_handlers {
while ( my $ref = shift @LJ::CLEANUP_HANDLERS ) {
next unless ref $ref eq 'CODE';
$ref->();
}
}
# <LJFUNC>
# name: LJ::color_fromdb
# des: Takes a value of unknown type from the DB and returns an #rrggbb string.
# args: color
# des-color: either a 24-bit decimal number, or an #rrggbb string.
# returns: scalar; #rrggbb string, or undef if unknown input format
# </LJFUNC>
sub color_fromdb {
my $c = shift;
return $c if $c =~ /^\#[0-9a-f]{6,6}$/i;
return sprintf( "\#%06x", $c ) if $c =~ /^\d+$/;
return undef;
}
# <LJFUNC>
# name: LJ::color_todb
# des: Takes an #rrggbb value and returns a 24-bit decimal number.
# args: color
# des-color: scalar; an #rrggbb string.
# returns: undef if bogus color, else scalar; 24-bit decimal number, can be up to 8 chars wide as a string.
# </LJFUNC>
sub color_todb {
my $c = shift;
return undef unless $c =~ /^\#[0-9a-f]{6,6}$/i;
return hex( substr( $c, 1, 6 ) );
}
# We're not always running under mod_perl... sometimes scripts (syndication sucker)
# call paths which end up thinking they need the remote IP, but don't.
sub get_remote_ip {
return $LJ::_T_FAKE_IP if $LJ::IS_DEV_SERVER && $LJ::_T_FAKE_IP;
my $r = DW::Request->get;
return ( $r ? $r->get_remote_ip : undef ) || $ENV{'FAKE_IP'};
}
# ($time, $secret) = LJ::get_secret(); # will generate
# $secret = LJ::get_secret($time); # won't generate
# ($time, $secret) = LJ::get_secret($time); # will generate (in wantarray)
sub get_secret {
my $time = int( $_[0] );
return undef if $_[0] && !$time;
my $want_new = !$time || wantarray;
if ( !$time ) {
$time = time();
$time -= $time % 3600; # one hour granularity
}
my $memkey = "secret:$time";
my $secret = ( $SecretCache{$memkey} ||= LJ::MemCache::get($memkey) );
return $want_new ? ( $time, $secret ) : $secret if $secret;
my $dbh = LJ::get_db_writer();
return undef unless $dbh;
$secret = $dbh->selectrow_array( q{SELECT secret FROM secrets WHERE stime = ?}, undef, $time );
if ($secret) {
$SecretCache{$memkey} = $secret;
LJ::MemCache::set( $memkey, $secret );
return $want_new ? ( $time, $secret ) : $secret;
}
# return if they specified an explicit time they wanted.
# (calling with no args means generate a new one if secret
# doesn't exist)
return undef unless $want_new;
# don't generate new times that don't fall in our granularity
return undef if $time % 3600;
$secret = LJ::rand_chars(32);
$dbh->do( q{INSERT IGNORE INTO secrets SET stime=?, secret=?}, undef, $time, $secret );
# check for races:
$secret = get_secret($time);
return ( $time, $secret );
}
sub is_web_context {
return 1 if $ENV{MOD_PERL};
return 1 if $DW::Request::cur_req;
return 0;
}
# loads an include file, given the bare name of the file.
# ($filename)
# returns the text of the file from memcache/db.
sub load_include {
my $file = shift;
return unless $file && $file =~ /^[a-zA-Z0-9-_\.]{1,255}$/;
# we handle, so first if memcache...
my $val = LJ::MemCache::get("includefile:$file");
return $val if $val;
# straight database hit
my $dbh = LJ::get_db_writer();
$val = $dbh->selectrow_array( "SELECT inctext FROM includetext " . "WHERE incname=?",
undef, $file );
LJ::MemCache::set( "includefile:$file", $val, time() + 3600 );
return $val if $val;
# if not in memcache, hit disk -- if it exists
my $filename = "$LJ::HTDOCS/inc/$file";
return unless -e $filename;
# get it and return it
open( INCFILE, $filename )
or return "Could not open include file: $file.";
{ local $/ = undef; $val = <INCFILE>; }
close INCFILE;
return $val;
}
# <LJFUNC>
# name: LJ::bit_breakdown
# des: Breaks down a bitmask into an array of bits enabled.
# args: mask
# des-mask: The number to break down.
# returns: A list of bits enabled. E.g., 3 returns (0, 2) indicating that bits 0 and 2 (numbering
# from the right) are currently on.
# </LJFUNC>
sub bit_breakdown {
my $mask = shift() + 0;
# check each bit 0..63 and return only ones that are defined
return grep { defined }
map { $mask & ( 1 << $_ ) ? $_ : undef } 0 .. 63;
}
sub last_error_code {
return $LJ::last_error;
}
sub last_error {
my $err = {
'utf8' => "Encoding isn't valid UTF-8",
'db' => "Database error",
'comm_not_found' => "Community not found",
'comm_not_comm' => "Account not a community",
'comm_not_member' => "User not a member of community",
'comm_invite_limit' => "Outstanding invitation limit reached",
'comm_user_has_banned' => "Unable to invite; user has banned community",
};
my $des = $err->{$LJ::last_error};
if ( $LJ::last_error eq "db" && $LJ::db_error ) {
$des .= ": $LJ::db_error";
}
return $des || $LJ::last_error;
}
sub error {
my $err = shift;
if ( LJ::DB::isdb($err) ) {
$LJ::db_error = $err->errstr;
$err = "db";
}
elsif ( $err eq "db" ) {
$LJ::db_error = "";
}
$LJ::last_error = $err;
return undef;
}
*errobj = \&LJ::Error::errobj;
*throw = \&LJ::Error::throw;
# Returns a LWP::UserAgent or LWP::UserAgent::Paranoid agent depending on role
# passed in by the caller.
# Des-%opts:
# role => what is this UA being used for? (required)
# timeout => seconds before request will timeout, defaults to 10
# max_size => maximum size of returned document, defaults to no limit
sub get_useragent {
my %opts = @_;
my $timeout = $opts{'timeout'} || 10;
my $max_size = $opts{'max_size'} || undef;
my $agent = $opts{'agent'};
my $role = $opts{'role'};
return unless $role;
my $lib = 'LWP::UserAgent::Paranoid';
$lib = $LJ::USERAGENT_LIB{$role} if defined $LJ::USERAGENT_LIB{$role};
eval "require $lib";
my $ua = $lib->new(
request_timeout => $timeout,
max_size => $max_size,
ssl_opts => {
# FIXME: we still need verify_hostname off. Investigate.
verify_hostname => 0,
# also needed for LWP::Protocol::https < 6.06
SSL_verify_mode => 0,
#ca_file => Mozilla::CA::SSL_ca_file()
}
);
#$ua->agent($agent) if $agent;
return $ua;
}
sub assert_is {
my ( $va, $ve ) = @_;
return 1 if $va eq $ve;
LJ::errobj(
"AssertIs",
expected => $ve,
actual => $va,
caller => [ caller() ]
)->throw;
}
# no_utf8_flag previously used pack('C*',unpack('C*', $_[0]))
# but that stopped working in Perl 5.10.
sub no_utf8_flag {
# tell Perl to ignore the SvUTF8 flag in this scope.
use bytes;
# make a copy of the input string that doesn't have the flag at all.
return substr( $_[0], 0 );
}
# return 1 if root caller is a test, else 0
sub in_test {
return $LJ::_T_CONFIG == 1 ? 1 : 0;
}
our $AUTOLOAD;
sub AUTOLOAD {
if ( $AUTOLOAD eq "LJ::send_mail" ) {
eval "use LJ::Sendmail;";
goto &$AUTOLOAD;
}
Carp::croak("Undefined subroutine: $AUTOLOAD");
}
sub conf_test {
my ( $conf, @args ) = @_;
return 0 unless $conf;
return $conf->(@args) if ref $conf eq "CODE";
return $conf;
}
sub is_enabled {
my $conf = shift;
if ( $conf eq 'payments' ) {
my $remote = LJ::get_remote();
return 1 if $remote && $remote->can_beta_payments;
}
return !LJ::conf_test( $LJ::DISABLED{$conf}, @_ ) + 0;
}
# document valid arguments for certain privs (using hooks)
# argument: name of priv
# returns: hashref of argname/argdesc, or just list of argnames if wantarray
sub list_valid_args {
my ($priv) = @_;
my $hr = {};
foreach ( LJ::Hooks::run_hooks( "privlist-add", $priv ) ) {
my $ret = $_->[0];
next unless $ret;
# merge all results
@{$hr}{ keys %$ret } = values %$ret;
}
# optionally allow someone to remove a listing that was provided elsewhere
foreach ( LJ::Hooks::run_hooks( "privlist-remove", $priv ) ) {
my @del = @$_;
# remove any keys listed by the hook
delete $hr->{$_} foreach @del;
}
return wantarray ? keys %$hr : $hr;
}
# END package LJ;
package LJ::Error::InvalidParameters;
sub opt_fields { qw(params) }
sub user_caused { 0 }
package LJ::Error::AssertIs;
sub fields { qw(expected actual caller) }
sub user_caused { 0 }
sub as_string {
my $self = shift;
my $caller = $self->field('caller');
my $ve = $self->field('expected');
my $va = $self->field('actual');
return
"Assertion failure at "
. join( ', ', (@$caller)[ 0 .. 2 ] )
. ": expected=$ve, actual=$va";
}
1;