#!/usr/bin/perl # # LiveJournal.com-specific library # # This file is NOT licensed under the GPL. As with everything in the # "ljcom" CVS repository, this file is the property of Danga # Interactive and is made available to the public only as a reference # as to the best way to modify/extend the base LiveJournal server code # (which is licensed under the GPL). # # Feel free to read and learn from things in "ljcom", but don't use it verbatim # because we don't want your site looking like LiveJournal.com (our logo # and site scheme are our identity and we don't want to confuse users) # and we're sick of getting everybody's payment notifications when # they use our payment system without any modifications. # require 'phonepost.pl'; use Class::Autouse qw( LJ::Setting::EmbedPlaceholders LJ::Jabber::Presence LJ::Subscription::Pending LJ::Pay::LoyaltyUserpic LJ::ModuleLoader LJ::SUP ); LJ::clear_hooks(); @LJ::USER_TABLES_LOCAL = ("phonepostentry", "phoneposttrans", "vgifts", "sms_quota", "sms_promo", "spinvox_msg"); LJ::ModuleLoader->require_if_exists("ljcomint.pl"); LJ::ModuleLoader->require_if_exists("sms-local.pl"); # Useful untainting regexen %LJ::REGEX = ( httpuri => qr{(http://(?:(?:(?:(?:(?:[a-zA-Z\d](?:(?:[a-zA-Z\d]|-)*[a-zA-Z\d])?)\.)*(?:[a-zA-Z](?:(?:[a-zA-Z\d]|-)*[a-zA-Z\d])?))|(?:(?:\d+)(?:\.(?:\d+)){3}))(?::(?:\d+))?)(?:/(?:(?:(?:(?:[a-zA-Z\d\$\_.+!*\'(),-]|(?:%[a-fA-F\d]{2}))|[;:@&=])*)(?:/(?:(?:(?:[a-zA-Z\d\$\_.+!*\'(),-]|(?:%[a-fA-F\d]{2}))|[;:@&=])*))*)(?:\?(?:(?:(?:[a-zA-Z\d\$\_.+!*\'(),-]|(?:%[a-fA-F\d]{2}))|[;:@&=])*))?)?)}x, ); # Users can specify 'as' GET arguments to authenticate as anyone # if $LJ::IS_DEV_SERVER is on. It's scary to think about that # being set along with $LJ::IS_LJCOM_PRODUCTION, so we'll be really # loud if that happens if ($LJ::IS_DEV_SERVER) { die "Cannot run with \$LJ::IS_DEV_SERVER set in LJcom production environment!" if $LJ::IS_LJCOM_PRODUCTION; # also beta die "Cannot run with \$LJ::IS_DEV_SERVER set in LJcom beta environment!" if $LJ::IS_LJCOM_BETA; } package LJ::Contrib; # is the given user an acked contributor themselves? sub is_acked { my ($userid) = @_; my $dbr = LJ::get_db_reader(); return undef unless $dbr and $userid; return $dbr->selectrow_array("SELECT COUNT(*) FROM contributed WHERE userid=? AND acks > 0", undef, $userid); } # make $coid acked by $userid sub ack { my ($coid, $userid) = @_; my $dbh = LJ::get_db_writer(); return undef unless $dbh and $userid and $coid; # see if contribution exists my $co = $dbh->selectrow_hashref("SELECT * FROM contributed WHERE coid=?", undef, $coid); return 0 unless $co; ## Lock the Tables $dbh->do("LOCK TABLES contributedack WRITE, contributed WRITE"); ## add the ack $dbh->do("REPLACE INTO contributedack (coid, ackuserid) VALUES (?,?)", undef, $coid, $userid); ## see how many acks it has now. my $newcount = $dbh->selectrow_array("SELECT COUNT(*) FROM contributedack WHERE coid=?", undef, $coid); $newcount += 0; ## update the contributed table $dbh->do("UPDATE contributed SET acks=? WHERE coid=?", undef, $newcount, $coid); ## Unlock tables $dbh->do("UNLOCK TABLES"); return 1; } package LJ::LJcom; use strict; no warnings 'uninitialized'; use Class::Autouse qw( LJ::VGift LJ::ModuleCheck LJ::ModuleLoader ); use Carp qw (croak); sub country_of_ip { my $ip = shift; return undef unless LJ::ModuleCheck->have("Geo::IP::PurePerl"); my $gi = $LJ::CACHE_GEOIP_HANDLE ||= Geo::IP::PurePerl->open("$LJ::HOME/cgi-bin/GeoIP.dat"); return $gi->country_code_by_addr($ip); } # Name: acct_name_short # Input Parm: $caps # Returns: short account name type: {new|off|early|paid|on|plus} sub acct_name_short { my $caps = shift; # note the ordering... foreach my $sname (qw(perm paid sponsored plus early free new)) { return $sname if LJ::caps_in_group($caps, $sname); } return "??"; } # display option can be either "extrainfo" or "acctname" # extrainfo: only return the paid expiration and/or previously early adopter string # acctname: only return the actual current account name sub acct_name { my $caps = shift; my $paiduntil = shift; my %opts = @_; my $is_early = LJ::caps_in_group($caps, "early"); my $display_extrainfo = $opts{display} eq "extrainfo" ? 1 : 0; my $display_acctname = $opts{display} eq "acctname" ? 1 : 0; if (LJ::caps_in_group($caps, "perm")) { if ($display_extrainfo) { return $is_early ? $BML::ML{'ljcom.userinfo.types.previously_early'} : ""; } elsif ($display_acctname) { return $BML::ML{'ljcom.userinfo.types.permanent'}; } else { return $is_early ? $BML::ML{'ljcom.userinfo.types.permanent_early'} : $BML::ML{'ljcom.userinfo.types.permanent'}; } } elsif (LJ::caps_in_group($caps, "paid")) { if ($paiduntil) { if ($display_extrainfo) { return $is_early ? BML::ml('ljcom.userinfo.types.expiring_previously_early', { paiduntil => $paiduntil }) : BML::ml('ljcom.userinfo.types.expiring', { paiduntil => $paiduntil }); } elsif ($display_acctname) { return $BML::ML{'ljcom.userinfo.types.paid'}; } else { return $is_early ? BML::ml('ljcom.userinfo.types.paid_early_expiring', { paiduntil => $paiduntil }) : BML::ml('ljcom.userinfo.types.paid_expiring', { paiduntil => $paiduntil }); } } else { if ($display_extrainfo) { return $is_early ? $BML::ML{'ljcom.userinfo.types.previously_early'} : ""; } elsif ($display_acctname) { return $BML::ML{'ljcom.userinfo.types.paid'}; } else { return $is_early ? $BML::ML{'ljcom.userinfo.types.paid_early'} : $BML::ML{'ljcom.userinfo.types.paid'}; } } } elsif (LJ::caps_in_group($caps, "sponsored")) { if ($paiduntil) { if ($display_extrainfo) { return $is_early ? BML::ml('ljcom.userinfo.types.expiring_previously_early', { paiduntil => $paiduntil }) : BML::ml('ljcom.userinfo.types.expiring', { paiduntil => $paiduntil }); } elsif ($display_acctname) { return $BML::ML{'ljcom.userinfo.types.sponsored'}; } else { return $is_early ? BML::ml('ljcom.userinfo.types.sponsored_early_expiring', { paiduntil => $paiduntil }) : BML::ml('ljcom.userinfo.types.sponsored_expiring', { paiduntil => $paiduntil }); } } else { if ($display_extrainfo) { return $is_early ? $BML::ML{'ljcom.userinfo.types.previously_early'} : ""; } elsif ($display_acctname) { return $BML::ML{'ljcom.userinfo.types.sponsored'}; } else { return $is_early ? $BML::ML{'ljcom.userinfo.types.sponsored_early'} : $BML::ML{'ljcom.userinfo.types.sponsored'}; } } } elsif (LJ::caps_in_group($caps, "plus")) { if ($display_extrainfo) { return $is_early ? $BML::ML{'ljcom.userinfo.types.previously_early'} : ""; } elsif ($display_acctname) { return $BML::ML{'ljcom.userinfo.types.plus'}; } else { return $is_early ? $BML::ML{'ljcom.userinfo.types.plus_early'} : $BML::ML{'ljcom.userinfo.types.plus'}; } } elsif (LJ::caps_in_group($caps, "early")) { return $BML::ML{'ljcom.userinfo.types.early'}; } elsif (LJ::caps_in_group($caps, "free")) { return $BML::ML{'ljcom.userinfo.types.free'}; } else { return undef; } } sub expresslane_html_comment { my ($u, $r) = @_; return '' unless $r && $u && LJ::get_cap($u, 'paid'); my ($free_ct, $free_age) = ($r->header_in('X-Queue-Count')+0, $r->header_in('X-Queue-Age')+0); return "\n"; } LJ::register_setter("opt_exclude_stats", sub { my ($u, $key, $value, $err) = @_; unless ($value =~ /^(0|1)$/) { $$err = "Illegal value. Must be '0' or '1'"; return 0; } # if we're in web context, which we should be, # then we'll change the user's hitbox status from # on to off if they are excluding themselves... # if they are turning off exclusion then we'll just # leave the cookie as-is. if (LJ::is_web_context() && $value == 1) { $BML::COOKIE{ljuniq} =~ s/:pgstats1$/:pgstats0/; } $u->set_prop("opt_exclude_stats", $value); return 1; }); # hook to handle creating a button to email someone about spam LJ::register_hook('spamreport_notification', sub { my ($remote, $opts) = @_; # they can send in either 'ip => foo' or 'posterid => foo' but we # only care about posterid for now my $posterid; return unless $posterid = $opts->{posterid}; my $poster = LJ::want_user($posterid); # verify we got the remote user and a poster $remote = LJ::want_user($remote); return undef unless $remote && $poster; if ($poster->openid_identity) { return "

WARNING: The account you are viewing (" . LJ::ljuser($poster) . ") is an OpenID identity and has no email address.

"; } # step 1) find related users by email my $dbr = LJ::get_db_reader(); return "" unless $dbr; my $users = $dbr->selectall_hashref('SELECT * FROM email WHERE email = ?', 'userid', undef, $poster->email_raw); return "" unless $users && ref $users eq 'HASH' && %$users; # now see if any of these have been warned my $in = join(',', map { ref $_ ? ($_->{userid} + 0) : 0 } values %$users); my $warnings = $dbr->selectall_arrayref("SELECT adminid, shdate, userid FROM statushistory " . "WHERE userid IN ($in) AND shtype = 'spam_warning'"); return "" if $dbr->err || !defined $warnings; # now construct html my ($ret, %emailcounts, %emails); foreach my $warning (@$warnings) { my $date = $warning->[1]; if ($date =~ /^(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)(\d\d)$/) { $date = "$1-$2-$3 $4:$5:$6"; } my $admin = LJ::load_userid($warning->[0]); my $warned = LJ::load_userid($warning->[2]); # come up with notes about this warning my $notes = 'none'; if (LJ::u_equals($poster, $warned)) { $notes = 'user match'; } elsif (lc $poster->email_raw eq lc $warned->email_raw) { $notes = 'email match'; if ($warned->{status} ne 'A') { $notes .= ' (unvalidated email)'; } } # query if we haven't queried based on this email address before if (!$emailcounts{$warned->email_raw}) { my $rows = $dbr->selectall_arrayref("SELECT mailid, userid, timesent, subject FROM abuse_mail " . "WHERE type='abuse' AND mailto = ?", undef, $warned->email_raw); $emailcounts{$warned->email_raw} = 1; foreach my $row (@{$rows || []}) { my ($mailid, $userid, $timesent, $subject) = @$row; my $u = LJ::load_userid($userid); $emails{$timesent} = "$timesent" . $warned->email_raw . "" . LJ::ljuser($u) . "" . "$subject\n"; } } # now construct output $ret .= "$date" . LJ::ljuser($admin) . "" . LJ::ljuser($warned) . ""; $ret .= "$notes\n"; } my $cols = join('', map { "$_" } qw(Date Admin Warned Notes) ); $ret = "$cols$ret
" if $ret; # now append emails if (%emails) { $ret .= ""; $ret .= "" . join('', map { "" } qw(Date Email Admin Subject) ) . ""; $ret .= join('', map { $emails{$_} } sort { $a cmp $b } keys %emails); $ret .= "
$_
"; } # get messages to put into body for sending my @incl = ( 'spam-warning' => "Send Warning Email", 'spam-warning-ru' => "Send Russian Warning Email", 'spam-suspend' => "Send Suspension Email", 'spam-suspend-ru' => "Send Russian Suspension Email", ); my @emails; while (my ($inc, $name) = splice(@incl, 0, 2)) { my $message = LJ::load_include($inc) or next; $message =~ s/\[\[user\]\]/$poster->{user}/ig; push @emails, ($message => $name); } # now construct the parts of the email $ret .= "
"; $ret .= "

"; $ret .= LJ::html_hidden(email => $poster->email_raw, bcc => $remote->email_raw, subject => "Your LiveJournal Account", request => '000000', extra => "spam-notification;$poster->{userid}", from => "abuse",); $ret .= LJ::html_select({ 'type' => 'radio', 'name' => 'message' }, @emails); $ret .= LJ::html_submit('Send Email'); $ret .= '

'; # include unvalidated email warning unless ($poster->{status} eq 'A') { $ret .= "

WARNING: The account you are viewing (" . LJ::ljuser($poster) . ") has an unvalidated email address.

"; } # return $ret = "

"; return $ret; }); ### Fetch the value for the given I from the specified I, ### untaint it with the given I, and return the results. If the ### I has match-groups in it, the values matched with them will be the ### returned values. Otherwise, the entire input will be returned. sub untaint { my ( $arghash, $field, $pattern ) = @_; return '' unless exists $arghash->{$field} && defined $arghash->{$field}; my $input = $arghash->{$field}; $pattern = qr{$pattern}i unless ref $pattern eq 'Regexp'; my @matches = ( $input =~ $pattern ) or return ''; return @matches if $1; return $input; } # hook to do transforms for posting pictures from FB LJ::register_hook('transform_update_postpics', sub { my ($GET, $POST) = @_; my ( @ids, $picsize, $columns, $caporient, $border, @pics, @rows, $row, $nextrow, $pic, $imgtag, $imgcell, $capcell, @html, ); # Untaint and split the picture ids to post @ids = split /:/, $1 if exists $POST->{ids} && $POST->{ids} =~ m{^([\d:]+)}; return unless $POST->{'wizard-picsize'} =~ m{^([tsf])$}i; $picsize = $1; $columns = $1 if exists $POST->{'wizard-columns'} && $POST->{'wizard-columns'} =~ m{^([1-4])$}; $caporient = $1 if exists $POST->{'wizard-caporient'} && $POST->{'wizard-caporient'} =~ m{^([arbl0])$}; $border = 1 if $POST->{'wizard-border'}; # Default/bound some values if not defined or valid -- Large picsize if ( $picsize eq 'f' ) { $columns = 1; } # Medium picsize elsif ( $picsize eq 's' ) { $columns = 1 if $columns < 1; $columns = 2 if $columns > 2; } # Thumbnail picsize else { $columns = 1 if $columns < 1; $columns = 4 if $columns > 4; } # Make sure the caption orientation will work with the number of columns # defined. if ( $columns == 1 || $columns == 3 ) { $caporient = 'b' unless $caporient eq '0' || $caporient eq 'a'; } # Build the array of ids to flow into the chosen layout @pics = map {{ id => $_, captitle => untaint( $POST, "subj$_", qr{([\t\r\n\x20-\xff]*)} ), capdesc => untaint( $POST, "desc$_", qr{([\t\r\n\x20-\xff]*)} ), img => untaint( $POST, "${picsize}img$_", $LJ::REGEX{httpuri} ), imgwidth => untaint( $POST, "${picsize}w$_", qr{(\d+)} ), imgheight => untaint( $POST, "${picsize}h$_", qr{(\d+)} ), url => untaint( $POST, "url$_", $LJ::REGEX{httpuri} ), }} @ids; # Build a table for the pics and their captions while ( @pics ) { $row = []; $nextrow = []; # Fill up each row with the specified number of columns while ( @$row < $columns ) { $pic = shift @pics; # If there's a picture to add, do so if ( $pic ) { $imgtag = sprintf q{%s}, @{$pic}{qw[img captitle imgheight imgwidth]}; $imgcell = sprintf q{%s}, $pic->{url}, $imgtag; $capcell = sprintf qq{%s
\n\t\t%s}, @{$pic}{qw[captitle capdesc]}; } # Otherwise it'll be a blank cell else { $imgcell = ''; $capcell = ''; } ## Now arrange the caption relative to the pic if there is a caption # Above if ( $caporient eq 'a' ) { push @$row, $capcell; push @$nextrow, $imgcell; } # Below elsif ( $caporient eq 'b' ) { push @$row, $imgcell; push @$nextrow, $capcell; } # Leftish captions, then the image, then rightish captions else { push @$row, $capcell if $caporient eq 'l'; push @$row, $imgcell; push @$row, $capcell if $caporient eq 'r'; } } push @rows, $row; push @rows, $nextrow if @$nextrow; } # Mangle the rows into an indented HTML table @html = (); push @html, ( " ", " ", " " ); foreach my $row ( @rows ) { push @html, ( " ", (map { " " } @$row), " ", ); } push @html, "
$_
\n
\n \n\n"; # Stick the results into the posted event $POST->{event} = join "\n", @html; return; }); # cluster definition hook. gets called: $clusterid LJ::register_hook('cluster_description', sub { my $cid = $_[0]+0; return $LJ::CLUSTERNAME{$cid} || "Cluster $cid"; }); # LJ user transition business logic sub LJ::LJcom::note_user_transition { my ($u, %arg) = @_; my @to_add = map { LJ::name_caps_short(1 << $_) } @{$arg{add} || []}; my @to_remove = map { LJ::name_caps_short(1 << $_) } @{$arg{remove} || []}; # any change to be made? return 1 unless @to_add || @to_remove; # list of caps we care about in ascending order of precedence my @cap_list = qw(new free early plus sponsored paid perm); # find the effective cap before modification my $eff_before; foreach my $cap (reverse @cap_list) { next unless $u->in_class($cap); # user has this cap $eff_before = $cap; last; } # find the effective cap after modification my $eff_after; foreach my $cap (reverse @cap_list) { # don't count the cap if we're removing it next if grep { $cap eq $_ } @to_remove; # only count if cap is being added or the user # already has it next unless scalar(grep { $cap eq $_ } @to_add) || $u->in_class($cap); $eff_after = $cap; last; } # did we find valid before/after values? return 1 unless grep { $_ eq $eff_after } @cap_list; return 1 unless grep { $_ eq $eff_before } @cap_list; # noop? return 1 if $eff_before eq $eff_after; return $u->note_transition('account', $eff_before => $eff_after); } LJ::register_hook("ssl_check", sub { my $r = $_[0]{r}; return $r->header_in("X-LJ-SSL") || ($LJ::IS_DEV_SERVER && $r->header_in("Host") eq $LJ::SSLDOMAIN); }); # when cancelling an account be sure to revoke promos LJ::register_hook("rename_account", \&LJ::Pay::remove_recbill); LJ::register_hook("change_journal_type", \&LJ::Pay::remove_recbill); LJ::register_hook("account_cancel", \&LJ::Pay::remove_recbill); LJ::register_hook("update.bml_disable_can_post", sub { my $arg = shift; ${$arg->{title}} = "Trial account expired"; ${$arg->{body}} = "Your 30 day LiveJournal trial account has expired. For more information on what you can do at this point, check out the LiveJournal Trial Page."; return 1; }); LJ::register_hook("large_journal_icon", sub { my $u = shift; # If Sponsored Community return "sponcomm24x24.gif" if ($LJ::SPONSORED_COMMUNITY{$u->{user}}); return "partnercomm24x24.gif" if ($LJ::PARTNER_COMMUNITY{$u->{user}}); return undef; }); LJ::register_hook("canonicalize_url", sub { my $u = shift; $$u =~ s!^http://livejournal\.com!http://www.livejournal.com!; if ($$u =~ m!^http://www\.livejournal\.com!) { $$u =~ s!&nc=\d+!!; } foreach my $pattern (qw( \.(jpg|jpeg|gif|png)$ selectsmart\.com /test /quiz quiz\.html test\.html elitechild\.com livejournal\.com/user )) { next unless $$u =~ /$pattern/i; $$u = ""; return; } # strip anchor names (to prevent some online tests from showing up # a billion times) $$u =~ s/\#.+//; }); LJ::register_hook("expand_embedded", sub { LJ::PhonePost::show_phoneposts(@_); }); LJ::register_hook("url_phonepost", sub { my ($u, $dppid, $ext) = @_; return $u->journal_base . "/data/phonepost/$dppid.$ext"; }); LJ::register_hook("data_handler:phonepost", sub { my ($user, $pathextra) = @_; if ($pathextra =~ m#^/(\d+)\.(mp3|ogg|wav)$#) { my $dppid = $1; return sub { my $r = shift; my $u = LJ::load_user($user); return LJ::PhonePost::apache_content($r, $u, $dppid); }; } return undef; }); LJ::register_hook("files_handler:phonepost", sub { my ($user, $pathextra) = @_; # redirect to data url, in user's own domain if ($pathextra =~ m#^/(\d+)\.(mp3|ogg|wav)$#) { my ($dppid, $ext) = ($1, $2); return sub { my $r = shift; my $u = LJ::load_user($user); my $dataurl = $u->journal_base . "/data/phonepost/$dppid.$ext"; $r->header_out(Location => $dataurl); return Apache::Constants::REDIRECT(); }; } return undef; }); LJ::register_hook("adjust_phonepost_usage", sub { my ($u, $usage) = @_; # Check for cutoff date return if (!$LJ::GIZMO_PROMO_CUTOFF || time() >= $LJ::GIZMO_PROMO_CUTOFF); # Get phonepost entries my %phoneps = (); %phoneps = LJ::PhonePost::phoneposts_for_month($u); return unless scalar keys %phoneps; # Only retrieve logprop data my $opts = {}; $opts->{prop_only} = 1; # Get logprop data for each of the phoneposts my @postids; foreach my $jitemid (keys %phoneps) { push @postids, [ $u->{clusterid}, $u->{userid}, $jitemid ]; } my $posts = LJ::get_posts_raw($opts, @postids); # count posts with useragent 'gizmo' my $gizmo_posts = 0; foreach my $jitemid (keys %phoneps) { my $id = $u->{userid} . ":$jitemid"; $gizmo_posts++ if $posts->{prop}{$id}{useragent} =~ /gizmo/i; } # Subtract gizmo posts from usage number $$usage = $$usage - $gizmo_posts; return 1; }); LJ::register_hook("bad_password", sub { return undef if $LJ::NO_PASSWORD_CHECK; my $arg = shift; my $haveu = defined $arg->{'u'} && LJ::isu($arg->{'u'}); return undef unless $haveu || defined $arg->{'password'}; my $password = defined $arg->{'password'} ? $arg->{'password'} : $arg->{'u'}->password; # Setup to do smarter checks my $user; my $name; my $email; # If we have a u object then pull values from that unless the caller # overwrote them specifically if ($haveu) { $user = defined $arg->{'user'} ? $arg->{'user'} : $arg->{'u'}->{'user'}; $name = defined $arg->{'name'} ? $arg->{'name'} : $arg->{'u'}->{'name'}; $email = defined $arg->{'email'} ? $arg->{'email'} : $arg->{'u'}->{'email'}; $user = lc($user); $name = lc($name); $email = lc($email); # See what arguments the caller passed in that we can use } else { if (defined $arg->{'user'}) { $user = lc($arg->{'user'}); } if (defined $arg->{'name'}) { $name = lc($arg->{'name'}); } if (defined $arg->{'email'}) { $email = lc($arg->{'email'}); } } my $lc_pass = lc($password); my $ml_code; my $distinct_chars = sub { my %seen = map { $_, 1 } split//, shift; return scalar keys %seen; }; my $code_words = sub { my $ml_code = shift; if ($haveu) { LJ::load_user_props($arg->{'u'}, 'browselang'); return LJ::Lang::get_text($arg->{'u'}->{'browselang'}, $ml_code); } else { return LJ::Lang::get_text('en_LJ', $ml_code); } }; # only ASCII if (!LJ::is_ascii($password)) { return $code_words->('ljcom.badpass.ascii'); } # at least 6 chars if (length($password) < 6) { return $code_words->('ljcom.badpass.length'); } # check username and reverse of username if (defined $user) { if (index($user, $lc_pass) >= 0 || index($lc_pass, $user) >= 0) { return $code_words->('ljcom.badpass.username'); } elsif (index($lc_pass, reverse $user) >= 0) { return $code_words->('ljcom.badpass.username.reverse'); } } # check email (foo.bar@baz.com we check "foo.bar" and "baz") if (defined $email) { my ($euser, $edomain) = split('@', $email); $edomain =~ s/^(\w+)\./$1/; if (index($euser, $lc_pass) >= 0 || index($edomain, $lc_pass) >= 0) { return $code_words->('ljcom.badpass.email'); } } # real name matches if (defined $name && LJ::trim($name)) { if (index ($name, $lc_pass) >= 0 || index($lc_pass, $name) >= 0) { return $code_words->('ljcom.badpass.displayname'); } } # must have 4 distinct characters if ($distinct_chars->($password) < 4) { return $code_words->('ljcom.badpass.distinct'); } # no digit or punctuation if ($password !~ /[^a-zA-Z]/) { # treat punctuation as a digit return $code_words->('ljcom.badpass.onlyalpha'); } if (LJ::ModuleLoader->require_if_exists("common-passwords.pl")) { my $leetify = sub { my $pass = shift; # Change letters into a letter and number # character class $pass =~ s/a/\[a4\]/g; $pass =~ s/b/\[b8\]/g; $pass =~ s/e/\[e3\]/g; $pass =~ s/g/\[g6\]/g; $pass =~ s/i/\[i1\]/g; $pass =~ s/l/\[l1\]/g; $pass =~ s/o/\[o0\]/g; $pass =~ s/s/\[s5\]/g; $pass =~ s/z/\[z2\]/g; # Change numbers, not created by above, # into a number and letter character class $pass =~ s/(\w)0(\w)/$1\[0o\]$2/g; $pass =~ s/(\w)1(\w)/$1\[1il\]$2/g; $pass =~ s/(\w)2(\w)/$1\[2z\]$2/g; $pass =~ s/(\w)3(\w)/$1\[3e\]$2/g; $pass =~ s/(\w)4(\w)/$1\[4a\]$2/g; $pass =~ s/(\w)5(\w)/$1\[5s\]$2/g; $pass =~ s/(\w)6(\w)/$1\[6g\]$2/g; $pass =~ s/(\w)7(\w)/$1\[7t\]$2/g; $pass =~ s/(\w)8(\w)/$1\[8b\]$2/g; return $pass; }; foreach my $cp (@LJ::COMMON_PASSWORDS) { my $com_len = length($cp); my $cpr = $leetify->($cp); # See if their password contains the common password if ($password =~ /$cpr/i) { # It is ok to have a common used password in your password if your # password is longer than the common word plus the ceiling of half # of its length # # common: badpass # min ok: 7 + 4 = 11 # # common: computer # min ok: 8 + 4 = 12 use POSIX qw ( ceil ); unless (length($password) >= (POSIX::ceil($com_len / 2) + $com_len)) { return $code_words->('ljcom.badpass.common'); } } } } # no match return undef; }); LJ::register_hook("s1_style_select", sub { my $arg = shift; my $styleid = $arg->{'styleid'}; my $u = $arg->{'u'}; if ($arg->{'view'} eq "lastn" && $u->{'journaltype'} eq "Y" && $u->password eq "" && $LJ::SYN_LASTN_S1) { $$styleid = $LJ::SYN_LASTN_S1; } }); LJ::register_hook("journal_subdomain_redirect_url", sub { my ($domain, $uri) = @_; if ($uri eq "/") { if ($domain =~ /^communit/) { return "$LJ::SITEROOT/community/"; } if ($domain =~ /^user/) { return "$LJ::SITEROOT/directory.bml"; } if ($domain =~ /^syndicated/) { return "$LJ::SITEROOT/syn/"; } } return undef; }); LJ::register_hook("journal_base", sub { my ($u, $vhost) = @_; return undef unless $LJ::ONLY_USER_VHOSTS; return "#" unless $u; ## ## A special case for Independent Mind journals: ## their domain aliases are primary journal addresses ## if ($LJ::INDEPENDENT_USERNAMES{ $u->{user} }) { my $domain = $u->prop("journaldomain"); if ($domain && $domain =~ /^[\w-]+(?:\.[\w-]+)+$/) { return "http://$domain"; } } # rule format: # accounttype => [$use_user_vhost_if_no_underscore, $domain_to_use_otherwise] my $rules = { 'P' => [1, "users.$LJ::DOMAIN"], 'S' => [1, "users.$LJ::DOMAIN"], 'Y' => [0, "syndicated.$LJ::DOMAIN"], 'C' => [0, "community.$LJ::DOMAIN"], }; my $rule = $rules->{$u->{journaltype}} || $rules->{'P'}; my $use_user_vhost = $rule->[0] && $u->{user} !~ /^\_/ && $u->{user} !~ /\_$/; if ($use_user_vhost) { my $udom = $u->{user}; $udom =~ s/\_/-/g; return "http://$udom.$LJ::USER_DOMAIN"; } else { return "http://$rule->[1]/$u->{user}"; } }); LJ::register_hook("force_s1", sub { my $u = shift; my $forceflag = shift; # Force syndicated accounts to S1 if ( $u->{journaltype} eq 'Y' ) { $$forceflag = 1; } }); LJ::register_hook("finduser_extrainfo", sub { my $arg = shift; my $u = $arg->{'u'}; my $dbh = $arg->{'dbh'}; my $ret; if ($u->{'caps'} & 16) { $ret .= " Permanent account.\n"; } if ($u->{'caps'} & 8) { my $unt = $dbh->selectrow_array("SELECT paiduntil FROM paiduser WHERE userid=?", undef, $u->{'userid'}); $ret .= " Paid until: $unt\n"; } }); LJ::register_hook("map_global_counter_domain", sub { my $dstr = shift; # need to map the domain string to one of our # reserved digits in the 1-char namespace (0-9) return { pay_refund_id => 0, }->{$dstr}; }); LJ::register_hook("global_counter_init_value", sub { my $dom = shift; # pay_refund_id - defaults to 1, then counts from there if ($dom eq '0') { return 1; } return undef; }); LJ::register_hook("postpost", sub { my $arg = shift; my $uo = $arg->{'journal'}; return if $uo->{'journaltype'} eq "Y"; # no syndicated my $up = $arg->{'poster'}; my $joblist = $arg->{'jobs'}; # if the poster has opted out, don't record the post LJ::load_user_props($up, "latest_optout"); return if $up->{latest_optout}; # setup security my $security = $arg->{'security'}; $security = $arg->{'allowmask'} == 1 ? 'friends' : 'custom' if ($security eq 'usemask'); # see if it has a public image in it. heuristic: it's an http:// # URL in an image tag, or it's alone and has a popular image extension my $img; if ($security eq "public" && ($arg->{'event'} =~ m!{'event'} =~ m!\'\"]!i || $arg->{'event'} =~ m!(http://\S+\.(?:gif|jpe?g|png)\b)!i)) { $img = $2 || $1; # make sure image is good and hasn't been used in last 4 hours unless (length($img) < 100 && $img !~ /[\n\r]/ && LJ::MemCache::add("ljcom_imgused:$img", 1, 3600*4)) { undef $img; } } # if this post if by a livejournal official community fire the event if (grep { $arg->{journal}->{user} eq $_ } @LJ::OFFICIAL_JOURNALS) { push @$joblist, LJ::Event::OfficialPost->new($arg->{entry})->fire_job; } if (grep { $arg->{journal}->{user} eq $_ } @LJ::OFFICIAL_SUP_JOURNALS) { push @$joblist, LJ::Event::SupOfficialPost->new($arg->{entry})->fire_job; } push @$joblist, TheSchwartz::Job->new_from_array("LJ::Worker::LatestPosts", { 'timepost' => time(), 'journalid' => $uo->{'userid'}, 'posterid' => $up->{'userid'}, 'itemid' => $arg->{'itemid'}, 'anum' => $arg->{'anum'}, 'security' => $security, 'img' => $img, 'taglist' => $arg->{'props'}->{'taglist'}, }); if ($security eq "public") { foreach my $server (@LJ::ATOMSTREAM) { push @$joblist, TheSchwartz::Job->new_from_array("LJ::Worker::AtomStreamInject", { 'journalid' => $uo->{'userid'}, 'jitemid' => $arg->{'itemid'}, 'server' => $server, }); } } }); # TEMP: Log unknown8bit posts to decide if they can be disabled later # see also table definition in update-db-local.pl LJ::register_hook("postpost", sub { my $entry = shift; return unless $LJ::DEBUG{'survey_8bit'} && $entry->{'props'}->{'unknown8bit'}; my $dbh = LJ::get_db_writer(); $dbh->do("REPLACE INTO survey_v0_8bit (userid, timepost) VALUES (?, UNIX_TIMESTAMP())", undef, $entry->{'poster'}->{'userid'}); }); # returns 1 if too fast, 0 if okay LJ::register_hook("ccpay_rate_check", sub { my ($tries, $lasttry) = @_; return 0 if $LJ::DEBUG{'no_cc_rate_check'}; # TRIES : LIMIT # - 1.. 3: 0 sec # - 4.. 6: 5 sec # - 7..10: 15 sec # - 11..19: 60 sec # - 20....: 30 min my $now = time(); return 0 if $tries <= 3; return 0 if $tries <= 6 && $lasttry < $now - 5; return 0 if $tries <= 10 && $lasttry < $now - 15; return 0 if $tries <= 19 && $lasttry < $now - 60; return 0 if $lasttry < $now - 1800; return 1; }); # given an S2 context, give back a BML langid. LJ::register_hook("set_s2bml_lang", sub { my ($ctx, $langref) = @_; my $lang = S2::get_property_value($ctx, 'lang_current'); $lang = 'en' unless grep(/$lang/, @LJ::LANGS); $lang = 'en_LJ' if ($lang eq 'en'); $$langref = $lang; }); # what tables bin/moveucluster.pl should move that aren't general code LJ::register_hook("moveucluster_local_tables", sub { return { 'phonepostentry' => 'userid', 'phoneposttrans' => 'journalid', }; }); # remove transcription group userprop LJ::register_hook("delete_friend_group", sub { my ($u, $bit) = @_; LJ::load_user_props($u, 'pp_transallow'); LJ::set_userprop($u, 'pp_transallow', -1) if $bit == $u->{pp_transallow}; }); LJ::register_hook("forbid_request", sub { my $r = shift; my $ua = $r->header_in("User-Agent"); my $ip = $r->connection->remote_ip; # @BAN_UA can be either scalar substrings of user-agents, or # an arrayref of [ $substr, $ip ] which makes the substring # match conditional on it matching that IP foreach (@LJ::BAN_UA) { if (ref) { return 1 if $_->[1] eq $ip && index($ua, $_->[0]) != -1; } else { return 1 if index($ua, $_) != -1; } } return 0; }); LJ::register_hook("bot_director", sub { my ($pre, $post) = @_; return "$pre If you are running a bot please visit this policy page outlining rules you must respect. $LJ::SITEROOT/bots/ $post" }); # control panel nag box LJ::register_hook('control_panel_extra_info', sub { my ($u, $ret) = @_; $$ret .= "
"; if (LJ::get_cap($u, "paid")) { $$ret .= BML::ml('ljcom.control_panel.paid', {'aopts' => "href='$LJ::SITEROOT/manage/payments/'", 'sitename' => $LJ::SITENAME}); # render account summary $$ret .= LJ::Pay::account_summary($u); } elsif ($u->in_class('plus')) { $$ret .= BML::ml('ljcom.control_panel.plus', {'aopts' => "href='$LJ::SITEROOT/manage/payments/'"}); } else { $$ret .= BML::ml('ljcom.control_panel.free', {'aopts' => "href='$LJ::SITEROOT/paidaccounts/'", 'sitename' => $LJ::SITENAMESHORT}); } $$ret .= "
"; }); # control panel extra column LJ::register_hook('control_panel_column', sub { my ($u, $ret) = @_; my $authas = ref $u ? "?authas=$u->{user}" : ""; my $list = "
  • "; $list .= "
  • " if $u && $u->in_class("plus"); $list .= "
  • File Manager
  • "; $list .= "
  • "; $$ret .= BML::fill_template('block', { 'HEADER' => "", 'ABOUT' => "", 'LIST' => $list }); }); LJ::register_hook('entryforminfo', sub { my $u = shift; my $remote = shift; my $poll_url = "$LJ::SITEROOT/poll/create.bml"; $poll_url .= "?authas=$u" if $u; # don't link to the creator if they can't make polls if (!$remote || !$remote->get_cap("makepoll")) { # returns array of communities they maintain where they can # post polls. (free users can post polls to paid comms, etc.) my $ct = LJ::get_authas_list($remote, { 'cap' => 'makepoll' }); $poll_url = "$LJ::SITEROOT/didyouknow/polls.bml" unless $ct; } my $ret = "

    " . BML::ml('ljcom.entryform.box.paidoptions.header2', {'aopts' => "href='$LJ::SITEROOT/paidaccounts/'"}) . "

    \n"; $ret .= "\n"; return $ret; }); # args: { userid , ppid } # appends enclosure or "" to rss $ret string LJ::register_hook('pp_rss_enclosure', sub { my $opts = shift; return LJ::PhonePost::make_link( undef, $opts->{userid}, ( $opts->{ppid} >> 8 ), 'rss' ); }); # args: name # return: formatted name for local config LJ::register_hook('identity_display_name', sub { my $name = shift; $name =~ s/\[(live|dead)journal\.com\]/\[${1}journal\]/; $name =~ s/^(.+)\.(live|dead)journal\.com$/${1} \[${2}journal\]/; return $name; }); LJ::register_hook('offsite_journal_search', sub { my $u = shift; my $ret; my $user = LJ::ehtml($u->{'user'}); $ret .= qq{ Note: This search is provided by an independent company, LJSeek, and is provided solely as a convenience. We are not responsible for the resulting content or search results. p?>
       
    exact phrase
     
    standout?> }; return $ret; }); LJ::register_hook('update_insobj_fb', sub { my $enabled = shift; unless ($enabled) { return "Upgrade your account to access ScrapBook storage."; } else { return 'Images uploaded from your computer will automatically be added to your ScrapBook\'s Unsorted gallery.'; } }); LJ::register_hook('transform_ljuniq_value', sub { my $args = shift; my $val = $args->{value}; my $extra = $args->{extra}; my $hook_saved_mapping = $args->{hook_saved_mapping}; # Be paranoid die("Cannot transform a false value for ljuniq cookie") unless $val; # is this value of a legitimate form? if not just echo back. return $val unless $val =~ /^([a-zA-Z0-9]{15}):(\d+)/; my ($uniq, $uniq_time) = ($1, $2); my @extra_parts = grep { length } split(":", $extra); my ($pgstats_extra, $mapping_extra); foreach my $part (@extra_parts) { if ($part =~ /^pgstats[01]$/) { $pgstats_extra = $part; } elsif ($part =~ /^m[01]$/) { $mapping_extra = $part; } } # decide whether to add PageStats flag if not already set my $remote = LJ::get_remote(); my $force_pgstats = $remote && grep { $remote->user eq $_ } @LJ::PAGESTATS_FORCE_USERS; # if $extra was passed in with pagestats information, then that # should be returned as the new value without changing the # user's actual pagestats status if (!$pgstats_extra || $force_pgstats) { $pgstats_extra = "pgstats"; my $pval = 0; my $rand = int(rand(100)) + 1; if ($rand <= $LJ::PAGESTATS_SAMPLE) { # $LJ::PAGESTATS_SAMPLE ||= 2 # Don't include them if they are underage or said no to stats if ($remote && ($remote->underage || $remote->prop('opt_exclude_stats'))) { $pval = "0"; } else { $pval = "1"; } } # some users can be forced to always have pgstats=1 $pval = "1" if $force_pgstats; # construct a new pgstats_extra portion with the value calculated above $pgstats_extra = "pgstats$pval"; } # ... otherwise if pgstats_extra was already defined, we use that # now, look at mapping_extra to see if we need to # intially set it or move a 0 => 1 if (! $mapping_extra || $mapping_extra eq 'm0') { if ($remote && ! LJ::UniqCookie->is_disabled) { my $now = time(); $mapping_extra = "m1"; # mapped: 1 # save this mapping then signal to our caller # that they don't need to do the same LJ::UniqCookie->save_mapping($uniq => $remote); $$hook_saved_mapping = 1; } else { $mapping_extra = "m0"; } } return "$val:" . join(":", grep { length } $pgstats_extra, $mapping_extra); }); LJ::register_hook("entry_deleted_page_extras", sub { return qq {
    }; }); LJ::register_hook("post_email_change", sub { my $args = shift; my $u = $args->{'user'}; return undef unless LJ::isu($u); my $newemail = $args->{'newemail'}; # is this email sysbanned? return 1 unless LJ::sysban_check('email', $newemail); my $suspend = $args->{'suspend'} || 0; # Are we also suspending this account? my $uniq = ''; eval { $uniq = Apache->request->notes('uniq'); }; my $ip = LJ::get_remote_ip(); my %req; $req{'reqtype'} = 'user'; # What userid is system? my $sys_u = LJ::load_user('system'); $req{'requserid'} = $sys_u->{userid}; $req{'spcatid'} = 8; # Abuse my $message; if ($suspend) { $req{'subject'} = "sysban automatic suspension: $u->{user}"; $message = "A user has just validated a sysbanned email address. Because this email address has been sysbanned, the account has been suspended automatically.\n\n"; $message .= "Username: $u->{user}\n"; $message .= "Email: $newemail\n"; $message .= "Uniq: $uniq\n"; $message .= "IP: $ip\n"; } else { $req{'subject'} = "sysban warning: $u->{user}"; $message = "A user has just changed his/her email address to a sysbanned email address. The address has not yet been validated. This is just a warning; no action should necessarily be taken.\n\n"; $message .= "Username: $u->{user}\n"; $message .= "Old Email: " . $u->email_raw . "\n"; $message .= "New Email: $newemail\n"; $message .= "Uniq: $uniq\n"; $message .= "IP: $ip\n"; } $req{'body'} = $message; my @errors; my $spid = LJ::Support::file_request(\@errors, \%req); if ($suspend) { LJ::update_user($u->{'userid'}, { statusvis => 'S', raw => 'statusvisdate=NOW()' }); $u->{statusvis} = 'S'; LJ::statushistory_add($u->{'userid'}, $sys_u->{userid}, 'suspend', "Automatically suspended for validating a sysbanned email address: #$spid"); LJ::run_hooks("account_cancel", $u); $u->fb_push; } return 1; }); LJ::register_hook("set_badpassword", sub { my $args = shift; my $u = $args->{'user'}; return undef unless LJ::isu($u); my $remote = LJ::get_remote(); return undef unless $remote; return undef unless defined $args->{'on'}; my $on = $args->{'on'}; # We only want to do something if we marked them as having # a bad password. return undef unless $on; my $body; $body .= "Dear $u->{'user'},\n"; $body .= "Your LiveJournal account has been frozen, as there is evidence that someone has obtained your account's password. To protect you, we have frozen the account so the intruder can't abuse it further."; $body .= "\n\n"; $body .= "To have your account unfrozen, please log into your LiveJournal account. When you do, you'll be prompted to change your password."; $body .= "\n\n"; $body .= "If the intruder has changed your account password before you are able to do so, please see http://www.livejournal.com/support/faqbrowse.bml?faqid=117 for information on how to recover your account."; $body .= "\n\n"; $body .= "If you have any questions, please feel free to contact the Abuse team by emailing abuse\@livejournal.com. Please be sure to include your account name."; $body .= "\n\n"; $body .= "Regards,\n LiveJournal Abuse Team"; my $subject = 'Your LiveJournal Account'; # While we could use a dbr here, we need a dbh later anyway # and we want to make sure this is the latest data anyway my $dbh = LJ::get_db_writer() or return undef; # Start with their current email address my $email = $u->email_raw; # See if the email address on the account was changed in the past 24 hours # If so, get any old email addresses that were validated my $sth = $dbh->prepare("SELECT oldvalue, UNIX_TIMESTAMP(timechange) " . "FROM infohistory WHERE userid=? AND UNIX_TIMESTAMP() " . "- UNIX_TIMESTAMP(timechange) <= 86400 AND other = 'A'"); $sth->execute($u->{'userid'}) or return undef; my $oldemails = $sth->fetchall_arrayref; if ($oldemails) { # Get the earliest email address and use that instead of current foreach (sort { $a->[1] <=> $b->[1] } @$oldemails) { $email = $_->[0]; last; } } my $res = LJ::send_mail({ 'to' => $email, 'from' => 'abuse@livejournal.com', 'fromname' => 'LiveJournal Abuse Team', 'charset' => 'utf-8', 'subject' => $subject, 'body' => $body, }); my $query = "INSERT INTO abuse_mail (mailid, userid, status, timesent, mailto, " . "subject, message, type) " . "VALUES (NULL, ?, ?, NOW(), ?, ?, ?, ?)"; $dbh->do($query, undef, $remote->{'userid'}, $res ? 'S' : 'F', $email, $subject, $body, 'abuse'); return 1; }); LJ::register_hook('interface_handler', sub { my $opts = shift; my $interface = delete $opts->{'int'}; my $r = delete $opts->{'r'}; my $bml_handler = delete $opts->{'bml_handler'}; if ($interface eq 'm365_mo') { return $bml_handler->("$LJ::HOME/ssldocs/misc/m365_mo.bml"); } if ($interface eq 'm365_mo-dev') { return $bml_handler->("$LJ::HOME/ssldocs/misc/m365_mo-dev.bml"); } if ($interface eq 'm365_ack') { return $bml_handler->("$LJ::HOME/htdocs/misc/m365_ack.bml"); } if ($interface eq 'm365_ack-dev') { return $bml_handler->("$LJ::HOME/htdocs/misc/m365_ack-dev.bml"); } if ($interface eq 'sup_payment') { return $bml_handler->("$LJ::HOME/htdocs/sup/pay_interface.bml"); } if ($interface eq 'intercast') { return $bml_handler->("$LJ::HOME/htdocs/misc/intercast.bml"); } if ($interface eq 'sup_event_data') { return $bml_handler->("$LJ::HOME/htdocs/sup/get_event_data.bml"); } if ($interface eq 'sup_rpc') { return $bml_handler->("$LJ::HOME/htdocs/sup/rpc_interface.bml"); } if ($interface eq 'xuqa') { return $bml_handler->("$LJ::HOME/htdocs/misc/xuqa_interface.bml"); } # This is also implemented in livejournal as Apache::LiveJournal::Interface::ElsewhereInfo # which is called from main LiveJournal.pm. # # We intercept /interface/elsewhere_info here so that we can call ->handle from a BML file # allowing us to iterate on the code without a restart if ($interface eq 'elsewhere_info') { return $bml_handler->("$LJ::HOME/htdocs/misc/elsewhere_info.bml"); } return undef; }); # add a link to buy a gift for someone on their birthday LJ::register_hook('birthday_extra_html', sub { my $user = shift; return "\"Buy"; }); # Outputs the Small Feature Matrix LJ::register_hook('feature_matrix', sub { return LJ::Widget::FeatureMatrix->render(version => 'upgrade'); }); LJ::register_hook('css_cleaner_transform', sub { my $css = shift; $$css = "/* suspect CSS: trying to alter $LJ::SITENAMESHORT ad placement */" if $$css =~ /ljad(?:skyscraper|leaderboard|smrect|medrect|banner|5linkunit|badge|entrybox|wrapper)?/i or $$css =~ /\badframe\b/i or $$css =~ /\bads-widget\b/i; $$css = "/* suspect CSS: iframes cannot be used in journal styles */" if $$css =~ /iframe/i; return 1; }); LJ::register_hook('transform_embed', sub { my $tokens = shift; my %opts = @_; my ($width, $height, $src, $flashvars); foreach my $token (@$tokens) { next unless $token->[0] eq 'S'; my $check = sub { my $attr = shift; $width = $attr->{width} if (exists $attr->{width} and defined $attr->{width}); $height = $attr->{height} if (exists $attr->{height} and defined $attr->{height}); $flashvars = $attr->{flashvars} if (exists $attr->{flashvars} and defined $attr->{flashvars}); }; my $attr = $token->[2] || {}; $check->($attr); if (exists $attr->{style}) { # Extract styles and put into a hash for easy use my %styles = map { split /:/, $_ } (split /\s*;\s*/, $attr->{style}); $check->(\%styles); } $src = $attr->{src} if exists $attr->{src} and defined $attr->{src}; } # Fake a few tokens from the html cleaner to pass into our next hook. my @tags = ( ['S', 'lj-template', { name => 'video', (defined $width ? ( width => $width ) : ()), (defined $height ? ( height => $height ) : ()), (defined $flashvars ? ( flashvars => $flashvars ) : ()), }], [ 'T', $src, {}], ['E', 'lj-template', {}], ); return defined $src ? LJ::run_hook("expand_template_video", \@tags, %opts) : ''; }); LJ::register_hook('expand_template_video', sub { my $remote = LJ::get_remote(); my $tokens = shift; my %opts = @_; # We should only have 3 tokens, start, target and end. The second target [1] should be of type [0] target (T) # and contains the url # If a video is originally included in the RTE, we need a url attribute # for going back and forth between Plain and RTE. This is because the RTE # doesn't allow custom HTML tags, so it is converting them to use a div. return "" unless ((@$tokens == 3 && $tokens->[1][0] eq "T") || $tokens->[0][2]{'url'}); my $url; unless ($tokens->[0][2]{'url'}) { $url = $tokens->[1][1]; #The contents part of the target } else { $url = $tokens->[0][2]{'url'}; } # [0] is the start token, [2] is the attributes my $content_type = $tokens->[0][2]{type}; my $width = $tokens->[0][2]{width}; my $height = $tokens->[0][2]{height}; $url =~ s/^\s+//; $url =~ s/\s+$//; my $ok = 0; my $host; foreach my $host_tmp (keys %LJ::WHITELIST_VIDEO_HOSTS) { if ($url =~ m!^http://([^/]+\.)?$host_tmp/\S+$!) { $ok = 1; $host = $host_tmp; last; } } # if we have a url_regex item in the config for this host, use it to match against if (my $url_regex = $LJ::WHITELIST_VIDEO_HOSTS{$host}->{url_regex}) { $ok = 0 unless $url =~ $url_regex; } # if this is a youtube video link, fix the url to be the link to the actual video if ($url =~ m!^http://([\w-]+\.)?youtube\.com!i) { $url =~ s!/watch\?v=([\w-]+)!/v/$1!i; # make sure not using youtube as a proxy to display videos from other sites $ok &&= $url !~ /iurl=/i; # no autoplay $ok &&= $url !~ /autoplay=.+/i; } # same goes for photobucket if ($url =~ m!^http://(?:.+\.)photobucket\.com!i) { $ok = 0 unless $url =~ m!^http://(\w+)\.photobucket\.com/player.swf!i; $url =~ s!^http://(\w+)\.photobucket\.com([/\w]+)\?\S+?current\=([\.\w]+)! http://$1.photobucket.com/player.swf?file=http://$1.photobucket.com$2$3 !xi; } # must be vox's flash player if ($url =~ m!^http://(?:.+\.)vox\.com!i) { $ok = 0 unless $url =~ m!^http://aka-static.vox.com/\.shared[^/]*/flash/VideoPlayer.swf!i; } # no autoplay for imeem if ($url =~ m!^http://media\.imeem\.com!i && $url !~ m!/aus=false/$!) { $url .= "/aus=false/"; } # convert the google HTML page to the flash player if ($url =~ m!^http://video\.google\.com/!i) { $url =~ s!videoplay!googleplayer\.swf!i; } # gallery.ru's video player if ($url =~ m!^http://(?:.+\.)gallery\.ru!i) { $ok = 0 unless $url =~ m!^http://.+\.gallery\.ru/getvideo/n\d+$!; } return "Invalid video URL." unless $ok || $opts{nocheck}; $width =~ s/px$//; $height =~ s/px$//; $width = $width =~ /^\d+$/ ? $width : $LJ::WHITELIST_VIDEO_HOSTS{$host}->{width} || 320; $height = $height =~ /^\d+$/ ? $height : $LJ::WHITELIST_VIDEO_HOSTS{$host}->{height} || 240; $content_type = $content_type =~ /^application\/\w+$/i ? $content_type : $LJ::WHITELIST_VIDEO_HOSTS{$host}->{content_type} || "application/x-shockwave-flash"; my $other = $LJ::WHITELIST_VIDEO_HOSTS{$host}->{other} || {}; # set/override the wmode if given one $other->{wmode} = $opts{wmode} if $opts{wmode}; my $other_tags = join ' ', map { qq($_="$other->{$_}") } keys %$other; my $other_whitelist = $LJ::WHITELIST_VIDEO_HOSTS{$host}->{other_whitelist} || []; my $whitelist_tags = join ' ', map { "$_=\"" . $tokens->[0][2]{$_} . '"' } @$other_whitelist; my $eurl = LJ::eall($url); my $wmode_param = $other->{wmode} ? "{wmode}\">" : ""; my $embed = qq{ $wmode_param }; if ($remote) { my $holder = $remote->prop('opt_embedplaceholders'); if ($holder && $holder ne 'N') { $embed = LJ::placeholder_link( placeholder_html => $embed, width => $width, height => $height, img => "$LJ::IMGPREFIX/videoplaceholder.png", ); } } return $embed; }); LJ::register_hook('expand_template_photobucket_video', sub { my $tokens = shift; return "" unless @$tokens == 3 && $tokens->[1][0] eq "T"; my $url = $tokens->[1][1]; my $w = $tokens->[0][2]{width}; my $h = $tokens->[0][2]{height}; $w = 352 unless $w =~ /^\d+$/; $h = 308 unless $h =~ /^\d+$/; $url =~ s/^\s+//; $url =~ s/\s+$//; return "" unless $url =~ m!^http://.+\.photobucket.com/[\w/:/\.\?\=\+\-\&]+$!; return qq{ }; }); LJ::register_hook('lj-replace_first_post', sub { my $ret; return unless LJ::is_web_context(); my $r = Apache->request; # Friends pages has a different message for first post if ($r->notes('view') eq "friends") { $ret .= BML::ml('ljcom.lj-replace.first_post.friends', { 'addfriend' => "src='$LJ::IMGPREFIX/btn_addfriend.gif'", 'userhead' => "src='$LJ::IMGPREFIX/userinfo.gif'", 'interest' => "href='$LJ::SITEROOT/interests.bml'", 'school' => "href='$LJ::SITEROOT/schools/'", 'commicon' => "src='$LJ::IMGPREFIX/community.gif'", 'feedicon' => "src='$LJ::IMGPREFIX/syndicated.gif'", 'feeds' => "href='$LJ::SITEROOT/syn/'", 'spotlight' => "href='" . LJ::journal_base('lj_spotlight') . "'", 'invite' => "href='$LJ::SITEROOT/friends/invite.bml'", }); } else { $ret .= BML::ml('ljcom.lj-replace.first_post', { 'update_link' => "href='$LJ::SITEROOT/update.bml'", 'custom_link' => "href='$LJ::SITEROOT/customize/'", 'editpics' => "href='$LJ::SITEROOT/editpics.bml'", 'private_img' => "src='$LJ::IMGPREFIX/icon_private.gif'", 'lockicon_img' => "src='$LJ::IMGPREFIX/icon_protected.gif'", 'lj-cut_link' => "href='$LJ::HELPURL{'lj-cut'}'", 'voice_link' => "href='$LJ::SITEROOT/voicepost/'", 'mobile_link' => "href='$LJ::SITEROOT/manage/mobile/'", 'sms_link' => "href='$LJ::SITEROOT/manage/sms/'", 'talk_link' => "href='$LJ::SITEROOT/chat/'", 'interest' => "href='$LJ::SITEROOT/interests.bml'", }); } return $ret; }); LJ::register_hook('multisearch_custom_search_redirect', sub { my $opts = shift; my $type = delete $opts->{type}; my $query = delete $opts->{query}; if ($type eq "moreopts") { BML::redirect("/site/search.bml"); } }); LJ::register_hook('userlog_rows', sub { my $row = shift; return "Deleted virtual gift #" . $row->{actiontarget} if $row->{action} eq 'vgift_deleted'; return "Deleted row for virtual gift #" . $row->{actiontarget} if $row->{action} eq 'vgift_row_deleted'; return undef; }); # args: $u # extra status information for subscriptions LJ::register_hook('sub_status_extra', sub { my $u = shift; return '' if $u->in_class('paid'); my $paid_max = LJ::get_cap('paid', 'subscriptions'); my $better = $u->in_class('plus') ? 'Paid' : 'Plus'; my $ret = ''; my $sub_count = grep { $_->active && $_->enabled } $u->find_subscriptions(method => 'Inbox'); my $sub_max = $u->get_cap('subscriptions'); my $event_plural = $sub_count == 1 ? 'event' : 'events'; # link to manage settings (if not at manage settings) my $managelink = qq { | Manage subscriptions in the Manage Settings page
    } unless BML::get_uri() =~ m!/manage/subscriptions/index.bml!i; my $getmore; if (! $u->in_class('plus')) { # basic $getmore .= qq {
    Get more features by upgrading to a $better Account. Learn more...
    }; } else { # plus $getmore .= qq {
    Get more notices by upgrading to a $better Account. Learn more...
    }; } my $subscribedto = $u->in_class('plus') ? '' : "You are subscribed to $sub_count $event_plural ($sub_max available) $managelink"; return qq {
    $subscribedto $getmore
    }; }); LJ::register_hook('ad_layout_blurb', sub { my $u = shift; my $ret = ""; if ($u->in_class('plus')) { my $username = $u->user; $ret .= "
    $BML::ML{'ljcom.adlayoutblurb.header'}"; $ret .= " "href='$LJ::SITEROOT/manage/payments/adsettings.bml?authas=$username'" }) . " p?>"; $ret .= "
    "; } return $ret; }); LJ::register_hook('ctxpopup_extra_info', sub { my $u = shift; my $remote = LJ::get_remote(); return () unless $u; my $online; $online = $u->jabber_is_online if $u->can_show_onlinestatus($remote); return ( is_online => $online, jabber_title => LJ::run_hook('jabber_title'), jabber_link => LJ::run_hook('jabber_link'), ); }); LJ::register_hook('opt_embedplaceholders', sub { my $u = LJ::want_user(shift); my $post_args = shift; my $ret; $ret .= "" . BML::ml('settings.embedplaceholder.title') . ""; my $key = LJ::Setting::EmbedPlaceholders->pkgkey; my $save_errors; if ($post_args) { eval { LJ::Setting::EmbedPlaceholders->save( $u, { embedplaceholders => $post_args->{"${key}embedplaceholders"}, } ) }; if (my $err = $@) { $ret .= "Couldn't process setting module save action: '$@'"; $save_errors = $err->field('map') if ref $err; } } eval { $ret .= LJ::Setting::EmbedPlaceholders->as_html( $u, $save_errors, $post_args ) }; if ($@) { $ret .= "Couldn't process setting module as_html action: '$@'"; } $ret .= "\n"; return $ret; }); LJ::register_hook('invite_email_extra', sub { my $u = LJ::load_user('lj_spotlight'); my $ret = ""; if ($u) { $ret .= " -- or -- Check out $LJ::SITENAMESHORT Spotlight: " . $u->journal_base . "/ Every week we hand pick a few journals and communities that $LJ::SITENAMESHORT users find interesting."; } return $ret; }); LJ::register_hook('join_comm_extra', sub { my $ret = ""; my $u = LJ::load_user('lj_spotlight'); $ret = "
  • ".BML::ml('.label.checkout_spotlight', { 'sitenameshort' => $LJ::SITENAMESHORT }) . "
  • " if $u; return $ret; }); LJ::register_hook('head_icon', sub { my $u = shift; my %opts = @_; if ($LJ::SPONSORED_COMMUNITY{$u->{user}}) { my $head_size = $opts{head_size}; return ("sponcomm_${head_size}.gif", $head_size) if $head_size; return ("sponcomm.gif", 16); } if ($LJ::PARTNER_COMMUNITY{$u->{user}}) { my $head_size = $opts{head_size}; return ("partnercomm_${head_size}.gif", $head_size) if $head_size; return ("partnercomm.gif", 16); } return (); }); LJ::register_hook('post_noauth', sub { my $req = shift; # ljprotocol request object my $ip = LJ::get_remote_ip(); # returns hashref of $journal (or "*") => $valid_rules, where $valid_rules can be: # ARRAYREF of usernames that can post to it. # CODE ref that gets run, receiving as args ($dest_journal, $as_user); # scalar (true or false); my $get_ip_rules = sub { return $LJ::POST_WITHOUT_AUTH{$ip} if $LJ::POST_WITHOUT_AUTH{$ip}; foreach my $k (keys %LJ::POST_WITHOUT_AUTH) { my $rule = $LJ::POST_WITHOUT_AUTH{$k}; ## ip mask ending with dot - e.g. 123.45.67. if ($k =~ /^\d.*\.$/) { return $rule if $ip =~ /^\Q$k\E/; } ## ip class if ($k =~ /^[A-Z]/i) { return $rule if $k eq LJ::LJcom::ip_class($ip); } } return undef; }; my $iprules = $get_ip_rules->() or return 0; my $poster = $req->{user}; my $journal = $req->{usejournal} || $poster; my $jrule = defined $iprules->{$journal} ? $iprules->{$journal} : $iprules->{"*"}; return 0 unless defined $jrule; if (ref $jrule eq "ARRAY") { # does array contain the poster as allowed poster? return 1 if grep { $_ eq $poster } @$jrule; return 0; } elsif (ref $jrule eq "CODE") { return $jrule->($journal, $poster); } elsif (! ref $jrule) { return $jrule; } return 0; }); # this is a hook to save a translation string. # if we have gearman, fire off a job to commit the string # and wait for the job to finish and return the status. LJ::register_hook('web_set_text', sub { my ($dmid, $lncode, $itcode, $text, $opts) = @_; # this is for the 'general' domain (not faq, etc) return 0 unless $dmid eq '1'; # this is only for en and en_LJ return 0 unless $lncode eq 'en' || $lncode eq 'en_LJ'; # don't do anything unless we have gearman return 0 unless scalar @LJ::GEARMAN_SERVERS; return 0 if $LJ::DISABLED{trans_commit}; my $client = LJ::gearman_client() or return 0; my $remote = LJ::get_remote(); my $remote_user = ''; $remote_user = $remote->user if $remote; # try to run gearman $opts->{taskhandle} = $client->dispatch_background("lj_trans_commit", Storable::nfreeze([$lncode, $itcode, $text, $remote_user])); return 1; }); LJ::register_hook('trans_editpage_bml_postsave_begin', sub { LJ::need_res(qw( js/core.js js/dom.js js/livejournal.js js/httpreq.js js/ippu.js js/lj_ippu.js js/jobstatus.js stc/lj_base.css )); }); LJ::register_hook('trans_editpage_bml_postsave', sub { my $opts = shift; my $taskhandle = $opts->{taskhandle}; return '' unless $taskhandle; my $id_html = LJ::ehtml($taskhandle); my $id_js = LJ::ejs($taskhandle); return qq {
    Starting commit task...
    }; }); sub get_ipmap { return unless $LJ::USE_IPMAP; my $ipmap = $LJ::IPMAP_INSTANCE; unless (defined $ipmap) { ## can't use Class::Autouse, because under mod_perl ## it loads a library immediately, not on a first usage require 'IpMap.pm'; $ipmap = IpMap->new(); $ipmap->LoadFromBinary("$LJ::HOME/bin/upgrading/ipmap_cdn.bin"); $LJ::IPMAP_INSTANCE = $ipmap; # save for next time } return $ipmap; } my %ip_class_by_country_code = ( '6A' => '6a', '1S' => 'sup', '1D' => 'sup_dtc', '1G' => 'sup_gazeta', '1M' => 'mon_dtc', #IN => 'russia', # India # CIS countries AM => 'russia', AZ => 'russia', BY => 'russia', EE => 'russia', GE => 'russia', KG => 'russia', KZ => 'russia', LT => 'russia', LV => 'russia', MD => 'russia', RU => 'russia', TJ => 'russia', TM => 'russia', UA => 'russia', UZ => 'russia', SG => 'singapore', ); sub ip_class { my $ip = shift || LJ::get_remote_ip(); # cookie override. (see /dev/ipclass.bml) my $cookie; if ($LJ::IS_DEV_SERVER && ($cookie = $BML::COOKIE{"fake_ipclass"})) { return "" if $cookie eq "none"; return $cookie; } if ($LJ::USE_IPMAP) { my $country = get_ipmap()->Resolve($ip); return unless $country; return $ip_class_by_country_code{$country}; } return; } 1;