# 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; } } } # # 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. # 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}; } # # 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. # 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] }; } } } } # # 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. # sub clear_caches { $LJ::CLEAR_CACHES = 1; } # # 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()) # 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; } # # 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. # 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; } # # 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). # 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; } # # name: LJ::flush_cleanup_handlers # des: Runs all cleanup handlers registered in @LJ::CLEANUP_HANDLERS # sub flush_cleanup_handlers { while ( my $ref = shift @LJ::CLEANUP_HANDLERS ) { next unless ref $ref eq 'CODE'; $ref->(); } } # # 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 # 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; } # # 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. # 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 = ; } close INCFILE; return $val; } # # 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. # 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;