#!/usr/bin/perl # # DW::Controller::API::Media # # API controls for the media system. # # Authors: # Mark Smith # # Copyright (c) 2013-2018 by Dreamwidth Studios, LLC. # # This program is free software; you may redistribute it and/or modify it under # the same terms as Perl itself. For a copy of the license, please reference # 'perldoc perlartistic' or 'perldoc perlgpl'. # package DW::Controller::API::Media; use strict; use warnings; use DW::Routing; use DW::Request; use DW::Controller; use DW::Controller::API; use DW::Media; use LJ::JSON; DW::Routing->register_api_endpoints( [ '/file/edit', \&file_edit_handler, 1 ], [ '/file/new', \&file_new_handler, 1 ], ); #{ # files: # [ # { # url: "http://url.to/file/or/page", # thumbnail_url: "http://url.to/thumnail.jpg", # name: "thumb2.jpg", # type: "image/jpeg", # size: 46353, #OPT delete_url: "http://url.to/delete/file/", #OPT delete_type: "DELETE" # } # ] #} # Allows uploading a file. Allocates and returns a unique media ID for the upload. sub file_new_handler { # we want to handle the not logged in case ourselves my ( $ok, $rv ) = controller( anonymous => 1 ); return $rv unless $ok; my $r = $rv->{r}; $r->did_post or return api_error( $r->HTTP_METHOD_NOT_ALLOWED, 'Needs a POST request' ); LJ::isu( $rv->{u} ) or return api_error( $r->HTTP_UNAUTHORIZED, 'Not logged in' ); return api_error( $r->HTTP_UNAUTHORIZED, 'Invalid account type' ) if $rv->{u}->is_identity; return api_error( $r->HTTP_BAD_REQUEST, 'Quota exceeded' ) unless DW::Media->can_upload_media( $rv->{u} ); my $uploads = $r->uploads; return api_error( $r->HTTP_BAD_REQUEST, 'No uploads found' ) unless ref $uploads eq 'ARRAY' && scalar @$uploads; foreach my $upload (@$uploads) { my ( $type, $ext ) = DW::Media->get_upload_type( $upload->{'content-type'} ); next unless defined $type && $type == DW::Media::TYPE_PHOTO; # Try to upload this item since we know it's a photo. my $obj = DW::Media->upload_media( user => $rv->{u}, data => $upload->{body}, security => $rv->{u}->newpost_minsecurity, ); return api_error( $r->SERVER_ERROR, 'Failed to upload media' ) unless $obj; # For now, we only support a single upload per call, so finish now. return api_ok( { id => $obj->displayid, url => $obj->url, thumbnail_url => $obj->url( extra => '100x100/' ), name => "image", type => $obj->mimetype, size => $obj->size, } ); } return api_error( $r->HTTP_BAD_REQUEST, 'No uploads found' ); } # Allows editing the metadata and security on a media object. The input to this # function is a dict, keys are the ids to modify, and the value is another dict # that contains what to modify. Example: # # { # 1234: { # security => "public", # public, private, access, usemask # allowmask => 3553, # only valid in usemask security # title => "some title", # else, the name of the property # otherprop => 5, # ... # } # 5653: ... # } # sub file_edit_handler { # we want to handle the not logged in case ourselves my ( $ok, $rv ) = controller( anonymous => 1 ); return $rv unless $ok; my $r = $rv->{r}; $r->did_post or return api_error( $r->HTTP_METHOD_NOT_ALLOWED, 'Needs a POST request' ); LJ::isu( $rv->{u} ) or return api_error( $r->HTTP_UNAUTHORIZED, 'Not logged in' ); my $args = $r->json or return api_error( $r->HTTP_BAD_REQUEST, 'Invalid/no JSON input' ); # First pass to check arguments. my %media; foreach my $id ( keys %$args ) { # sometimes JS sends us the string 'null' so let's make sure $id is OK return api_error( $r->HTTP_BAD_REQUEST, 'Media ID not provided' ) unless defined $id && $id ne 'null'; # use eval to catch croaks $media{$id} = eval { DW::Media->new( user => $rv->{u}, mediaid => int( $id / 256 ) ) }; return api_error( $r->NOT_FOUND, 'Media ID not found or invalid' ) unless $media{$id}; return api_error( $r->HTTP_BAD_REQUEST, 'Security invalid' ) if $args->{$id}->{security} && $args->{$id}->{security} !~ /^(?:public|private|usemask)$/; if ( exists $args->{$id}->{allowmask} ) { return api_error( $r->HTTP_BAD_REQUEST, 'Allowmask invalid with chosen security' ) unless $args->{$id}->{security} eq 'usemask'; return api_error( $r->HTTP_BAD_REQUEST, 'Allowmask must be numeric' ) unless $args->{$id}->{allowmask} =~ /^\d+$/; } # Check to be sure this is valid. Security and Allowmask are separate # from the rest, which are properties. foreach my $key ( keys %{ $args->{$id} } ) { next if $key eq 'security' || $key eq 'allowmask'; my $pobj = LJ::get_prop( media => $key ); return api_error( $r->HTTP_BAD_REQUEST, 'Invalid property' ) unless ref $pobj eq 'HASH' && $pobj->{id}; } } # We did that in two phases so we could verify that all of the objects # were loadable, to try to make it an atomic process. foreach my $id ( keys %$args ) { my ( $security, $allowmask ) = ( delete $args->{$id}->{security}, int( delete $args->{$id}->{allowmask} // 0 ) ); if ( defined $security ) { $media{$id}->set_security( security => $security, allowmask => $allowmask, ); } # At this point, we must have deleted all non-property items. foreach my $prop ( keys %{ $args->{$id} } ) { $media{$id}->prop( $prop => $args->{$id}->{$prop} ); } } return api_ok($args); } 1;