# 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 DW::Controller::RPC::MiscLegacy; use strict; use DW::Routing; use DW::RPC; use LJ::CreatePage; # do not put any endpoints that do not have the "forked from LJ" header in this file DW::Routing->register_rpc( "changerelation", \&change_relation_handler, format => 'json' ); DW::Routing->register_rpc( "checkforusername", \&check_username_handler, format => 'json' ); DW::Routing->register_rpc( "controlstrip", \&control_strip_handler, format => 'json' ); DW::Routing->register_rpc( "ctxpopup", \&ctxpopup_handler, format => 'json' ); DW::Routing->register_rpc( "esn_inbox", \&esn_inbox_handler, format => 'json' ); DW::Routing->register_rpc( "esn_subs", \&esn_subs_handler, format => 'json' ); DW::Routing->register_rpc( "getsecurityoptions", \&get_security_options_handler, format => 'json' ); DW::Routing->register_rpc( "gettags", \&get_tags_handler, format => 'json' ); DW::Routing->register_rpc( "load_state_codes", \&load_state_codes_handler, format => 'json' ); DW::Routing->register_rpc( "profileexpandcollapse", \&profileexpandcollapse_handler, format => 'json' ); DW::Routing->register_rpc( "userpicselect", \&get_userpics_handler, format => 'json' ); DW::Routing->register_rpc( "widget", \&widget_handler, format => 'json' ); sub change_relation_handler { my $r = DW::Request->get; my $post = $r->post_args; # get user my $remote = LJ::get_remote(); return DW::RPC->err("Sorry, you must be logged in to use this feature.") unless $remote; return DW::RPC->err("Invalid auth token") unless $remote->check_ajax_auth_token( '/__rpc_changerelation', %$post ); my ( $target, $action ); $target = $post->{target} or return DW::RPC->err("No target specified"); $action = $post->{action} or return DW::RPC->err("No action specified"); # Prevent XSS attacks $target = LJ::ehtml($target); $action = LJ::ehtml($action); my $targetu = LJ::load_user($target); return DW::RPC->err("Invalid user $target") unless $targetu; my $success = 0; my %ret = (); if ( $action eq 'addTrust' ) { my $error; return DW::RPC->err($error) unless $remote->can_trust( $targetu, errref => \$error ); $success = $remote->add_edge( $targetu, trust => {} ); } elsif ( $action eq 'addWatch' ) { my $error; return DW::RPC->err($error) unless $remote->can_watch( $targetu, errref => \$error ); $success = $remote->add_edge( $targetu, watch => {} ); $success &&= $remote->add_to_default_filters($targetu); } elsif ( $action eq 'removeTrust' ) { $success = $remote->remove_edge( $targetu, trust => {} ); } elsif ( $action eq 'removeWatch' ) { $success = $remote->remove_edge( $targetu, watch => {} ); } elsif ( $action eq 'join' ) { my $error; if ( $remote->can_join( $targetu, errref => \$error ) ) { $success = $remote->join_community($targetu); } else { if ( $error eq LJ::Lang::ml('edges.join.error.targetnotopen') && $targetu->is_moderated_membership ) { $targetu->comm_join_request($remote); $ret{note} = LJ::Lang::ml('edges.join.response.reqsubmitted'); } else { return DW::RPC->err($error); } } } elsif ( $action eq 'leave' ) { my $error; return DW::RPC->err($error) unless $remote->can_leave( $targetu, errref => \$error ); $success = $remote->leave_community($targetu); } elsif ( $action eq 'accept' ) { $success = $remote->accept_comm_invite($targetu); } elsif ( $action eq 'setBan' ) { my $list_of_banned = LJ::load_rel_user( $remote, 'B' ) || []; return DW::RPC->err("Exceeded limit maximum of banned users") if @$list_of_banned >= ( $LJ::MAX_BANS || 5000 ); my $ban_user = LJ::load_user($target); $success = $remote->ban_user($ban_user); LJ::Hooks::run_hooks( 'ban_set', $remote, $ban_user ); } elsif ( $action eq 'setUnban' ) { my $unban_user = LJ::load_user($target); $success = $remote->unban_user_multi( $unban_user->{userid} ); } else { return DW::RPC->err("Invalid action $action"); } return DW::RPC->out( success => $success, is_trusting => $remote->trusts($targetu), is_watching => $remote->watches($targetu), is_member => $remote->member_of($targetu), is_banned => $remote->has_banned($targetu), %ret, ); } sub check_username_handler { my $r = DW::Request->get; my $args = $r->get_args; my $error = LJ::CreatePage->verify_username( $args->{user} ); return DW::RPC->err($error); } sub control_strip_handler { my $r = DW::Request->get; my $args = $r->get_args; my $control_strip; my $user = $args->{user}; if ( defined $user ) { unless ( defined LJ::get_active_journal() ) { LJ::set_active_journal( LJ::load_user($user) ); } $control_strip = LJ::control_strip( user => $user, host => $args->{host}, uri => $args->{uri}, args => $args->{args}, view => $args->{view} ); } return DW::RPC->out( control_strip => $control_strip ); } sub ctxpopup_handler { my $r = DW::Request->get; my $get = $r->get_args; my $get_user = sub { # three ways to load a user: # username: if ( defined $get->{user} && ( my $user = LJ::canonical_username( $get->{user} ) ) ) { return LJ::load_user($user); } # identity: if ( defined $get->{userid} && ( my $userid = $get->{userid} ) ) { return undef unless $userid =~ /^\d+$/; my $u = LJ::load_userid($userid); return undef unless $u && $u->identity; return $u; } # based on userpic url if ( defined $get->{userpic_url} && ( my $upurl = $get->{userpic_url} ) ) { return undef unless $upurl =~ m!(\d+)/(\d+)!; my ( $picid, $userid ) = ( $1, $2 ); my $u = LJ::load_userid($userid); my $up = LJ::Userpic->instance( $u, $picid ); return $up->valid ? $u : undef; } }; my $remote = LJ::get_remote(); my $u = $get_user->(); my %ret = $u ? $u->info_for_js : (); my $reason = $u ? $u->prop('delete_reason') : ''; $reason = $reason ? "Reason given: " . $reason : "No reason given."; return DW::RPC->err("Error: Invalid mode") unless $get->{mode} eq 'getinfo'; return DW::RPC->out( error => "No such user", noshow => 1 ) unless $u; return DW::RPC->err( "This user's account is deleted.
" . $reason ) if $u->is_deleted; return DW::RPC->err("This user's account is deleted and purged.") if $u->is_expunged; return DW::RPC->err("This user's account is suspended.") if $u->is_suspended; # uri for changerelation auth token my $uri = '/__rpc_changerelation'; # actions to generate auth tokens for my @actions = (); $ret{url_addtrust} = "$LJ::SITEROOT/circle/" . $u->{user} . "/edit?action=access"; $ret{url_addwatch} = "$LJ::SITEROOT/circle/" . $u->{user} . "/edit?action=subscribe"; my $up = $u->userpic; if ($up) { $ret{url_userpic} = $up->url; $ret{userpic_w} = $up->width; $ret{userpic_h} = $up->height; } else { # if it's a feed, make their userpic the feed icon if ( $u->is_syndicated ) { $ret{url_userpic} = "$LJ::IMGPREFIX/feed100x100.png"; } elsif ( $u->is_identity ) { $ret{url_userpic} = "$LJ::IMGPREFIX/identity_100x100.png"; } else { $ret{url_userpic} = "$LJ::IMGPREFIX/nouserpic.png"; } $ret{userpic_w} = 100; $ret{userpic_h} = 100; } if ($remote) { $ret{is_trusting} = $remote->trusts($u); $ret{is_trusted_by} = $u->trusts($remote); $ret{is_watching} = $remote->watches($u); $ret{is_watched_by} = $u->watches($remote); $ret{is_requester} = $remote->equals($u); $ret{other_is_identity} = $u->is_identity; $ret{self_is_identity} = $remote->is_identity; $ret{can_message} = $u->can_receive_message($remote); $ret{url_message} = $u->message_url; $ret{can_receive_vgifts} = $u->can_receive_vgifts_from($remote); $ret{url_vgift} = $u->virtual_gift_url; } $ret{is_logged_in} = $remote ? 1 : 0; if ( $u->is_comm ) { $ret{url_joincomm} = "$LJ::SITEROOT/circle/" . $u->{user} . "/edit"; $ret{url_leavecomm} = "$LJ::SITEROOT/circle/" . $u->{user} . "/edit"; $ret{url_acceptinvite} = "$LJ::SITEROOT/manage/invites"; $ret{is_member} = $remote->member_of($u) if $remote; $ret{is_closed_membership} = $u->is_closed_membership; my $pending = $remote ? ( $remote->get_pending_invites || [] ) : []; $ret{is_invited} = ( grep { $_->[0] == $u->id } @$pending ) ? 1 : 0; push @actions, 'join', 'leave', 'accept'; } # generate auth tokens if ($remote) { push @actions, 'addTrust', 'addWatch', 'removeTrust', 'removeWatch', 'setBan', 'setUnban'; foreach my $action (@actions) { $ret{"${action}_authtoken"} = $remote->ajax_auth_token( $uri, target => $u->user, action => $action, ); } } my %extrainfo = LJ::Hooks::run_hook( "ctxpopup_extra_info", $u ) || (); %ret = ( %ret, %extrainfo ) if %extrainfo; $ret{is_banned} = $remote->has_banned($u) ? 1 : 0 if $remote; $ret{success} = 1; return DW::RPC->out(%ret); } sub esn_inbox_handler { my $r = DW::Request->get; my $post = $r->post_args; my $remote = LJ::get_remote(); return DW::RPC->err("Sorry, you must be logged in to use this feature.") unless $remote; my $authas = delete $post->{authas}; my $action = $post->{action}; return DW::RPC->err("No action specified") unless $action; my $success = 0; my %ret; # do authas my $u = LJ::get_authas_user($authas) || $remote; return DW::RPC->err("You could not be authenticated as the specified user.") unless $u; # get qids my @qids; @qids = split( ',', $post->{qids} ) if $post->{qids}; my @items; if ( scalar @qids ) { foreach my $qid (@qids) { my $item = eval { LJ::NotificationItem->new( $u, $qid ) }; push @items, $item if $item; } } $ret{items} = []; my $inbox = $u->notification_inbox; my $cur_folder = $post->{cur_folder} || 'all'; my $itemid = $post->{itemid} && $post->{itemid} =~ /^\d+$/ ? $post->{itemid} + 0 : 0; # do actions if ( $action eq 'mark_read' ) { $_->mark_read foreach @items; $success = 1; } elsif ( $action eq 'mark_unread' ) { $_->mark_unread foreach @items; $success = 1; } elsif ( $action eq 'delete' ) { foreach my $item (@items) { push @{ $ret{items} }, { qid => $item->qid, deleted => 1 }; $item->delete; } $success = 1; } elsif ( $action eq 'delete_all' ) { @items = $inbox->delete_all( $cur_folder, itemid => $itemid ); foreach my $item (@items) { push @{ $ret{items} }, { qid => $item->{qid}, deleted => 1 }; } $success = 1; } elsif ( $action eq 'mark_all_read' ) { $inbox->mark_all_read( $cur_folder, itemid => $itemid ); $success = 1; } elsif ( $action eq 'set_default_expand_prop' ) { $u->set_prop( 'esn_inbox_default_expand', $post->{default_expand} eq 'Y' ? 'Y' : 'N' ); } elsif ( $action eq 'get_unread_items' ) { $ret{unread_count} = $u->notification_inbox->unread_count; } elsif ( $action eq 'toggle_bookmark' ) { my $up; $up = LJ::Hooks::run_hook( 'upgrade_message', $u, 'bookmark' ); $up = "
$up" if ($up); foreach my $item (@items) { my $ret = $u->notification_inbox->toggle_bookmark( $item->qid ); return DW::RPC->err("Max number of bookmarks reached.$up") unless $ret; } $success = 1; } else { return DW::RPC->err("Invalid action $action"); } foreach my $item ( $u->notification_inbox->items ) { my $class = $item->event->class; $class =~ s/LJ::Event:://; push @{ $ret{items} }, { read => $item->read, qid => $item->qid, bookmarked => $u->notification_inbox->is_bookmark( $item->qid ), category => $class, }; } return DW::RPC->out( success => $success, unread_all => $inbox->all_event_count, unread_usermsg_recvd => $inbox->usermsg_recvd_event_count, unread_friend => $inbox->circle_event_count, unread_entrycomment => $inbox->entrycomment_event_count, unread_pollvote => $inbox->pollvote_event_count, unread_usermsg_sent => $inbox->usermsg_sent_event_count, %ret, ); } sub esn_subs_handler { my $r = DW::Request->get; my $post = $r->post_args; return DW::RPC->err("Sorry async ESN is not enabled") unless LJ::is_enabled('esn_ajax'); my $remote = LJ::get_remote(); return DW::RPC->err("Sorry, you must be logged in to use this feature.") unless $remote; # check auth token return DW::RPC->err("Invalid auth token") unless $remote->check_ajax_auth_token( '/__rpc_esn_subs', %$post ); my $action = $post->{action} or return DW::RPC->err("No action specified"); my $success = 0; my %ret; if ( $action eq 'delsub' ) { my $subid = $post->{subid} or return DW::RPC->err("No subid"); my $subscr = LJ::Subscription->new_by_id( $remote, $subid ); return DW::RPC->out( success => 0 ) unless $subscr; my %postauth; foreach my $subkey (qw(journalid arg1 arg2 etypeid)) { $ret{$subkey} = $subscr->$subkey || 0; $postauth{$subkey} = $ret{$subkey} if $ret{$subkey}; } $ret{event_class} = $subscr->event_class; $subscr->delete; $success = 1; $ret{msg} = "Notification Tracking Removed"; $ret{subscribed} = 0; my $auth_token = $remote->ajax_auth_token( '/__rpc_esn_subs', action => 'addsub', %postauth, ); if ( $subscr->event_class eq 'LJ::Event::JournalNewEntry' ) { $ret{newentry_token} = $auth_token; } else { $ret{auth_token} = $auth_token; } } elsif ( $action eq 'addsub' ) { return DW::RPC->err( "Reached limit of " . $remote->count_max_subscriptions . " active notifications" ) unless $remote->can_add_inbox_subscription; my %subparams = (); return DW::RPC->err("Invalid notification tracking parameters") unless ( defined $post->{journalid} ) && $post->{etypeid} + 0; foreach my $param (qw(journalid etypeid arg1 arg2)) { $subparams{$param} = $post->{$param} + 0; } $subparams{method} = 'Inbox'; my ($subscr) = $remote->has_subscription(%subparams); $subparams{flags} = LJ::Subscription::TRACKING; eval { $subscr ||= $remote->subscribe(%subparams) }; return DW::RPC->err($@) if $@; if ($subscr) { $subscr->activate; $success = 1; $ret{msg} = "Notification Tracking Added"; $ret{subscribed} = 1; $ret{event_class} = $subscr->event_class; my %sub_info = $subscr->sub_info; $ret{sub_info} = \%sub_info; # subscribe to email as well my %email_sub_info = %sub_info; $email_sub_info{method} = "Email"; $remote->subscribe(%email_sub_info); # special case for JournalNewComment: need to return dtalkid for # updating of tracking icons (on subscriptions with jtalkid) if ( $subscr->event_class eq 'LJ::Event::JournalNewComment' && $subscr->arg2 ) { my $cmt = LJ::Comment->new( $subscr->journal, jtalkid => $subscr->arg2 ); $ret{dtalkid} = $cmt->dtalkid if $cmt; } my $auth_token = $remote->ajax_auth_token( '/__rpc_esn_subs', subid => $subscr->id, action => 'delsub' ); if ( $subscr->event_class eq 'LJ::Event::JournalNewEntry' ) { $ret{newentry_token} = $auth_token; $ret{newentry_subid} = $subscr->id; } else { $ret{auth_token} = $auth_token; $ret{subid} = $subscr->id; } } else { $success = 0; $ret{subscribed} = 0; } } else { return DW::RPC->err("Invalid action $action"); } return DW::RPC->out( success => $success, %ret, ); } sub get_security_options_handler { my $r = DW::Request->get; my $args = $r->get_args; my $remote = LJ::get_remote(); my $user = $args->{user}; my $u = LJ::load_user($user); return DW::RPC->out unless $u; my %ret = ( is_comm => $u->is_comm ? 1 : 0, can_manage => $remote && $remote->can_manage($u) ? 1 : 0, ); return DW::RPC->out( ret => \%ret ) unless $remote && $remote->can_post_to($u); unless ( $ret{is_comm} ) { my $friend_groups = $u->trust_groups; $ret{friend_groups_exist} = keys %$friend_groups ? 1 : 0; } $ret{minsecurity} = $u->newpost_minsecurity; return DW::RPC->out( ret => \%ret ); } sub get_tags_handler { my $r = DW::Request->get; my $args = $r->get_args; my $remote = LJ::get_remote(); my $user = $args->{user}; my $u = LJ::load_user($user); my $tags = $u ? $u->tags : {}; return DW::RPC->alert("You cannot view this journal's tags.") unless $remote && $remote->can_post_to($u); return DW::RPC->alert("You cannot use this journal's tags.") unless $remote->can_add_tags_to($u); my @tag_names; if ( keys %$tags ) { @tag_names = map { $_->{name} } values %$tags; @tag_names = sort { lc $a cmp lc $b } @tag_names; } return DW::RPC->out( tags => \@tag_names ); } sub load_state_codes_handler { my $r = DW::Request->get; my $post = $r->post_args; my $country = $post->{country}; return DW::RPC->err("no country parameter") unless $country; my %states; my $states_type = $LJ::COUNTRIES_WITH_REGIONS{$country}->{type}; LJ::load_codes( { $states_type => \%states } ) if defined $states_type; return DW::RPC->out( states => [ map { $_, $states{$_} } sort { $states{$a} cmp $states{$b} } keys %states ], head => LJ::Lang::ml('states.head.defined'), ); } sub profileexpandcollapse_handler { my $r = DW::Request->get; my $get = $r->get_args; # if any opts aren't defined, they'll be passed in as empty strings # (actually header and expand are sometimes undefined in my testing, # hence the updates below. --kareila 2015/08/19) my $mode = $get->{mode} eq "save" ? "save" : "load"; my $header = ( defined $get->{header} && $get->{header} eq "" ) ? undef : $get->{header}; my $expand = ( defined $get->{expand} && $get->{expand} eq "false" ) ? 0 : 1; my $remote = LJ::get_remote(); return DW::RPC->out() unless $remote; my $collapsed_headers = $remote->prop("profile_collapsed_headers") // ''; my @collapsed_headers = split( /,/, $collapsed_headers ); if ( $mode eq "save" ) { return unless $header && $header =~ /_header$/; $header =~ s/_header$//; my %is_collapsed = map { $_ => 1 } @collapsed_headers; # this header is already saved as expanded or collapsed, so we don't need to do anything return if $is_collapsed{$header} && !$expand; return if !$is_collapsed{$header} && $expand; # remove header from list if expanding # add header to list if collapsing if ($expand) { delete $is_collapsed{$header}; $remote->set_prop( profile_collapsed_headers => join( ",", keys %is_collapsed ) ); } else { # collapse $is_collapsed{$header} = 1; $remote->set_prop( profile_collapsed_headers => join( ",", keys %is_collapsed ) ); } return $r->OK; } else { # load return DW::RPC->out( headers => \@collapsed_headers ); } } sub get_userpics_handler { my $r = DW::Request->get; my $get = $r->get_args; my $remote = LJ::get_remote(); my $alt_u; $alt_u = LJ::load_user( $get->{user} ) if $get->{user} && $remote->has_priv("supporthelp"); # get user my $u = ( $alt_u || $remote ); return DW::RPC->alert("Sorry, you must be logged in to use this feature.") unless $u; # get userpics my @userpics = LJ::Userpic->load_user_userpics($u); my %upics = (); # info to return $upics{pics} = {}; # upicid -> hashref of metadata foreach my $upic (@userpics) { next if $upic->inactive; my $id = $upic->id; $upics{pics}{$id} = { url => $upic->url, state => $upic->state, width => $upic->width, height => $upic->height, # we don't want the full version of alttext here, because the keywords, etc # will already likely be displayed by the icon # We don't want to use ehtml, because we want the JSON converter # handle escaping ", ', etc. We just escape the < and > ourselves alt => LJ::etags( $upic->description ), comment => LJ::strip_html( $upic->comment ), id => $id, keywords => [ map { LJ::strip_html($_) } $upic->keywords ], }; } $upics{ids} = [ sort { $a <=> $b } keys %{ $upics{pics} } ]; return DW::RPC->out(%upics); } sub widget_handler { my $r = DW::Request->get; my $get = $r->get_args; my $post = $r->post_args; return DW::RPC->err("Sorry widget AJAX is not enabled") unless LJ::is_enabled('widget_ajax'); my $remote = LJ::get_remote(); my $widget_class = LJ::ehtml( $post->{_widget_class} || $get->{_widget_class} ); return DW::RPC->err("Invalid widget class $widget_class") unless $widget_class =~ /^(IPPU::)?\w+$/gm; $widget_class = "LJ::Widget::$widget_class"; return DW::RPC->err("Cannot do AJAX request to $widget_class") unless $widget_class->ajax; # hack to circumvent a bigip/perlbal interaction # that sometimes closes keepalive POST requests under # certain conditions. accepting GETs makes it work fine if ( %$get && $widget_class->can_fake_ajax_post ) { $post->clear; $post->{$_} = $get->{$_} foreach keys %$get; } my $widget_id = $post->{_widget_id}; my $doing_post = delete $post->{_widget_post}; my %ret = ( _widget_id => $widget_id, _widget_class => $widget_class, ); # make sure that we're working with the right user if ( $post->{authas} ) { if ( $widget_class->authas ) { my $u = LJ::get_authas_user( $post->{authas} ); return DW::RPC->err("Invalid user.") unless $u; } else { return DW::RPC->err("Widget does not support authas authentication."); } } if ($doing_post) { # just a normal post request, handle it and then return status local $LJ::WIDGET_NO_AUTH_CHECK = 1 if LJ::Auth->check_ajax_auth_token( $remote, "/_widget", auth_token => delete $post->{auth_token} ); my %res; # set because LJ::Widget->handle_post uses this global variable @BMLCodeBlock::errors = (); eval { %res = LJ::Widget->handle_post( $post, $widget_class ); }; $ret{res} = \%res; $ret{errors} = $@ ? [$@] : \@BMLCodeBlock::errors; $ret{_widget_post} = 1; # generate new auth token for future requests if succesfully checked auth token $ret{auth_token} = LJ::Auth->ajax_auth_token( $remote, "/_widget" ) if $LJ::WIDGET_NO_AUTH_CHECK; } if ( delete $post->{_widget_update} ) { # render the widget and return it # remove the widget prefix from the POST vars foreach my $key ( keys %$post ) { my $orig_key = $key; if ( $key =~ s/^Widget\[\w+?\]_// ) { $post->{$key} = $post->{$orig_key}; delete $post->{$orig_key}; } } $ret{_widget_body} = eval { $widget_class->render_body(%$post); }; $ret{_widget_body} = "Error: $@" if $@; $ret{_widget_update} = 1; } return DW::RPC->out(%ret); } 1;