Apertur-SDK

 view release on metacpan or  search on metacpan

lib/Apertur/SDK/Resource/Upload.pm  view on Meta::CPAN

package Apertur::SDK::Resource::Upload;

use strict;
use warnings;

use JSON qw(encode_json);
use File::Basename qw(basename);
use Apertur::SDK::Crypto qw(encrypt_image);

sub new {
    my ($class, %args) = @_;
    return bless { http => $args{http} }, $class;
}

sub image {
    my ($self, $uuid, $file, %options) = @_;

    my ($data, $filename);
    if (ref $file eq 'SCALAR') {
        # Raw bytes passed as scalar ref
        $data     = $$file;
        $filename = $options{filename} || 'image.jpg';
    }
    else {
        # File path string
        $filename = $options{filename} || basename($file);
        open my $fh, '<:raw', $file
            or die "Cannot open file '$file': $!\n";
        local $/;
        $data = <$fh>;
        close $fh;
    }

    my $mime_type = $options{mimeType} || $options{mime_type} || 'image/jpeg';

    my @multipart = (
        file => [
            undef,
            $filename,
            'Content-Type' => $mime_type,
            Content        => $data,
        ],
    );

    if ($options{source}) {
        push @multipart, source => $options{source};
    }

    my %headers;
    if ($options{password}) {
        $headers{'x-session-password'} = $options{password};
    }

    return $self->{http}->request(
        'POST', "/api/v1/upload/$uuid/images",
        multipart => \@multipart,
        headers   => \%headers,
    );
}

sub image_encrypted {
    my ($self, $uuid, $file, $public_key, %options) = @_;

    my ($data, $filename);
    if (ref $file eq 'SCALAR') {
        $data     = $$file;
        $filename = $options{filename} || 'image.jpg';
    }
    else {
        $filename = $options{filename} || basename($file);
        open my $fh, '<:raw', $file
            or die "Cannot open file '$file': $!\n";
        local $/;
        $data = <$fh>;
        close $fh;
    }

    my $encrypted = encrypt_image($data, $public_key);

    # The server's `default` encrypted mode reads a multipart upload, parses
    # the file bytes as a JSON envelope (camelCase), then RSA-OAEP-SHA256
    # decrypts. Send the envelope as a file part, mirroring the plain
    # `image` multipart transport.
    my $envelope = encode_json({
        encryptedKey  => $encrypted->{encrypted_key},
        iv            => $encrypted->{iv},
        encryptedData => $encrypted->{encrypted_data},
        algorithm     => $encrypted->{algorithm},
    });

    my @multipart = (
        file => [
            undef,
            'image.jpg.enc',
            'Content-Type' => 'application/octet-stream',
            Content        => $envelope,
        ],
    );

    if ($options{source}) {
        push @multipart, source => $options{source};
    }

    my %headers = (
        'X-Aptr-Encrypted' => 'default',
    );
    if ($options{password}) {
        $headers{'x-session-password'} = $options{password};
    }

    return $self->{http}->request(
        'POST', "/api/v1/upload/$uuid/images",
        multipart => \@multipart,
        headers   => \%headers,
    );
}

1;

__END__

=head1 NAME

Apertur::SDK::Resource::Upload - Image upload operations

=head1 DESCRIPTION

Handles uploading images to an upload session, both plain multipart
and end-to-end encrypted.

=head1 METHODS

=over 4

=item B<image($uuid, $file, %options)>

Uploads an image via multipart POST. C<$file> can be a file path string
or a scalar reference containing raw bytes. Options: C<filename>,
C<mimeType> (or C<mime_type>), C<source>, C<password>.

=item B<image_encrypted($uuid, $file, $public_key, %options)>

Encrypts an image with AES-256-GCM (key wrapped with RSA-OAEP) and
uploads it as a JSON payload. Requires C<Crypt::OpenSSL::RSA> and
C<CryptX>. Options: same as C<image>.

=back

=cut



( run in 1.331 second using v1.01-cache-2.11-cpan-6aa56a78535 )