# AtomAPI support for LJ package Apache::LiveJournal::Interface::AtomAPI; use strict; use Apache::Constants qw(:common); use Digest::SHA1; use MIME::Base64; use lib "$ENV{'LJHOME'}/cgi-bin"; require 'parsefeed.pl'; require 'fbupload.pl'; BEGIN { $LJ::OPTMOD_XMLATOM = eval q{ use XML::Atom::Feed; use XML::Atom::Entry; use XML::Atom::Link; XML::Atom->VERSION < 0.09 ? 0 : 1; }; }; # check allowed Atom upload filetypes sub check_mime { my $mime = shift; return unless $mime; # TODO: add audio/etc support my %allowed_mime = ( image => qr{^image\/(?:gif|jpe?g|png|tiff?)$}i, #audio => qr{^(?:application|audio)\/(?:(?:x-)?ogg|wav)$}i ); foreach (keys %allowed_mime) { return $_ if $mime =~ $allowed_mime{$_} } return; } sub respond { my ($r, $status, $body, $type) = @_; my %msgs = ( 200 => 'OK', 201 => 'Created', 400 => 'Bad Request', 401 => 'Authentication Failed', 403 => 'Forbidden', 404 => 'Not Found', 500 => 'Server Error', ), my %mime = ( html => 'text/html', atom => 'application/x.atom+xml', xml => "text/xml; charset='utf-8'", ); # if the passed in body was a reference, send it # without any modification. otherwise, send some # prettier html to the client. my $out; if (ref $body) { $out = $$body; } else { $out = <
$body
HTML } $type = $mime{$type} || 'text/html'; $r->status_line("$status $msgs{$status}"); $r->content_type($type); $r->send_http_header(); $r->print($out); return OK; }; sub handle_upload { my ($r, $remote, $u, $opts, $entry) = @_; # entry could already be populated from a standalone # service.post posting. my $standalone = $entry ? 1 : 0; unless ($entry) { my $buff; $r->read($buff, $r->header_in("Content-length")); eval { $entry = XML::Atom::Entry->new( \$buff ); }; return respond($r, 400, "Could not parse the entry due to invalid markup.$@") if $@; } my $mime = $entry->content()->type(); my $mime_area = check_mime( $mime ); return respond($r, 400, "Unsupported MIME type: $mime") unless $mime_area; if ($mime_area eq 'image') { return respond($r, 400, "Unable to upload media. Your account doesn't have the required access.") unless LJ::get_cap($u, 'fb_can_upload') && $LJ::FB_SITEROOT; my $err; LJ::load_user_props( $u, qw/ emailpost_gallery emailpost_imgsecurity / ); my $summary = LJ::trim( $entry->summary() ); my $fb = LJ::FBUpload::do_upload( $u, \$err, { path => $entry->title(), rawdata => \$entry->content()->body(), imgsec => $u->{emailpost_imgsecurity}, caption => $summary, galname => $u->{emailpost_gallery} || 'AtomUpload', } ); return respond($r, 500, "There was an error uploading the media: $err") if $err || ! $fb; if (ref $fb && $fb->{Error}->{code}) { my $errstr = $fb->{Error}->{content}; return respond($r, 500, "There was an error uploading the media: $errstr"); } my $atom_reply = XML::Atom::Entry->new(); $atom_reply->title( $fb->{Title} ); if ($standalone) { $atom_reply->summary('Media post'); my $id = "atom:$u->{user}:$fb->{PicID}"; $fb->{Summary} = $summary; LJ::MemCache::set( $id, $fb, 1800 ); $atom_reply->id( "urn:fb:$LJ::FB_DOMAIN:$id" ); } my $link = XML::Atom::Link->new(); $link->type('text/html'); $link->rel('alternate'); $link->href( $fb->{URL} ); $atom_reply->add_link($link); $r->header_out("Location", $fb->{URL}); return respond($r, 201, \$atom_reply->as_xml(), 'atom'); } } sub handle_post { my ($r, $remote, $u, $opts) = @_; my ($buff, $entry); # read the content $r->read($buff, $r->header_in("Content-length")); # try parsing it eval { $entry = XML::Atom::Entry->new( \$buff ); }; return respond($r, 400, "Could not parse the entry due to invalid markup.
$@") if $@; # on post, the entry must NOT include an id return respond($r, 400, "Must not include an <id> field in a new entry.") if $entry->id(); # detect 'standalone' media posts return handle_upload( @_, $entry ) if $entry->get("http://sixapart.com/atom/typepad#", 'standalone'); # remove the SvUTF8 flag. See same code in synsuck.pl for # an explanation $entry->title( pack( 'C*', unpack( 'C*', $entry->title() ) ) ); $entry->link( pack( 'C*', unpack( 'C*', $entry->link() ) ) ); $entry->content( pack( 'C*', unpack( 'C*', $entry->content()->body() ) ) ); # Retrieve fotobilder media links from clients that embed via # standalone tags or service.upload transfers. Add to post entry # body. my $body = $entry->content()->body(); my @links = $entry->link(); my (@images, $link_count); foreach my $link (@links) { # $link is now a valid XML::Atom::Link object my $rel = $link->get('rel'); my $type = $link->get('type'); my $id = $link->get('href'); next unless $rel eq 'related' && check_mime($type) && $id; $id =~ s/^urn:fb:$LJ::FB_DOMAIN://; my $fb = LJ::MemCache::get( $id ); next unless $fb; push @images, { url => $fb->{URL}, width => $fb->{Width}, height => $fb->{Height}, caption => $fb->{Summary}, title => $fb->{Title} }; } $body .= LJ::FBUpload::make_html( $u, \@images ); # build a post event request. my $req = { 'usejournal' => ( $remote->{'userid'} != $u->{'userid'} ) ? $u->{'user'} : undef, 'ver' => 1, 'username' => $u->{'user'}, 'lineendings' => 'unix', 'subject' => $entry->title(), 'event' => $body, 'props' => {}, 'security' => 'public', 'tz' => 'guess', }; my $err; my $res = LJ::Protocol::do_request("postevent", $req, \$err, { 'noauth' => 1 }); if ($err) { my $errstr = LJ::Protocol::error_message($err); return respond($r, 500, "Unable to post new entry. Protocol error: $errstr."); } my $atom_reply = XML::Atom::Entry->new(); $atom_reply->title( $entry->title() ); $atom_reply->summary( substr( $entry->content->body(), 0, 100 ) ); my $link; my $edit_url = "$LJ::SITEROOT/interface/atom/edit/$res->{'itemid'}"; $link = XML::Atom::Link->new(); $link->type('application/x.atom+xml'); $link->rel('service.edit'); $link->href( $edit_url ); $link->title( $entry->title() ); $atom_reply->add_link($link); $link = XML::Atom::Link->new(); $link->type('text/html'); $link->rel('alternate'); $link->href( $res->{url} ); $link->title( $entry->title() ); $atom_reply->add_link($link); $r->header_out("Location", $edit_url); return respond($r, 201, \$atom_reply->as_xml(), 'atom'); } sub handle_edit { my ($r, $remote, $u, $opts) = @_; my $method = $opts->{'method'}; # first, try to load the item and fail if it's not there my $jitemid = $opts->{'param'}; my $req = { 'usejournal' => ($remote->{'userid'} != $u->{'userid'}) ? $u->{'user'} : undef, 'ver' => 1, 'username' => $u->{'user'}, 'selecttype' => 'one', 'itemid' => $jitemid, }; my $err; my $olditem = LJ::Protocol::do_request("getevents", $req, \$err, { 'noauth' => 1 }); if ($err) { my $errstr = LJ::Protocol::error_message($err); return respond($r, 404, "Unable to retrieve the item requested for editing. Protocol error: $errstr."); } $olditem = $olditem->{'events'}->[0]; if ($method eq "GET") { # return an AtomEntry for this item # use the interface between make_feed and create_view_atom in # ljfeed.pl # get the log2 row (need logtime for createtime) my $row = LJ::get_log2_row($u, $jitemid) || return respond($r, 404, "Could not load the original entry."); # we need to put into $item: itemid, ditemid, subject, event, # createtime, eventtime, modtime my $ctime = LJ::mysqldate_to_time($row->{'logtime'}, 1); my $item = { 'itemid' => $olditem->{'itemid'}, 'ditemid' => $olditem->{'itemid'}*256 + $olditem->{'anum'}, 'eventtime' => LJ::alldatepart_s2($row->{'eventtime'}), 'createtime' => $ctime, 'modtime' => $olditem->{'props'}->{'revtime'} || $ctime, 'subject' => $olditem->{'subject'}, 'event' => $olditem->{'event'}, }; my $ret = LJ::Feed::create_view_atom( { 'u' => $u }, $u, { 'saycharset' => "utf-8", 'noheader' => 1, 'apilinks' => 1, }, [$item] ); return respond($r, 200, \$ret, 'xml'); } if ($method eq "PUT") { # read the content my $buff; $r->read($buff, $r->header_in("Content-length")); # try parsing it my $entry; eval { $entry = XML::Atom::Entry->new( \$buff ); }; return respond($r, 400, "Could not parse the entry due to invalid markup.
$@") if $@; # remove the SvUTF8 flag. See same code in synsuck.pl for # an explanation $entry->title( pack( 'C*', unpack( 'C*', $entry->title() ) ) ); $entry->link( pack( 'C*', unpack( 'C*', $entry->link() ) ) ); $entry->content( pack( 'C*', unpack( 'C*', $entry->content()->body() ) ) ); # the AtomEntry must include