File Coverage

blib/lib/Mail/JMAPTalk.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -cw
2              
3 1     1   14633 use strict;
  1         2  
  1         32  
4 1     1   4 use warnings;
  1         1  
  1         50  
5              
6             package Mail::JMAPTalk;
7              
8 1     1   610 use HTTP::Tiny;
  1         47994  
  1         59  
9 1     1   899 use JSON::XS qw(decode_json encode_json);
  1         8799  
  1         87  
10 1     1   621 use Convert::Base64;
  1         1541  
  1         69  
11 1     1   294 use File::LibMagic;
  0            
  0            
12             use Carp qw(confess);
13             use Data::Dumper;
14              
15             our $VERSION = '0.07';
16              
17             our $CLIENT = "Net-JMAPTalk";
18             our $AGENT = "$CLIENT/$VERSION";
19              
20             sub new {
21             my ($Proto, %Args) = @_;
22             my $Class = ref($Proto) || $Proto;
23              
24             my $Self = bless { %Args }, $Class;
25              
26             return $Self;
27             }
28              
29             sub ua {
30             my $Self = shift;
31             unless ($Self->{ua}) {
32             $Self->{ua} = HTTP::Tiny->new(agent => $AGENT);
33             }
34             return $Self->{ua};
35             }
36              
37             sub auth_header {
38             my $Self = shift;
39             return 'Basic ' . encode_base64("$Self->{user}:$Self->{password}", '');
40             }
41              
42             sub authuri {
43             my $Self = shift;
44             my $scheme = $Self->{scheme} // 'http';
45             my $host = $Self->{host} // 'localhost';
46             my $port = $Self->{port} // ($scheme eq 'http' ? 80 : 443);
47             my $url = $Self->{authurl} // '/jmap/auth/';
48              
49             return $url if $url =~ m/^http/;
50              
51             return "$scheme://$host:$port$url";
52             }
53              
54             sub uploaduri {
55             my $Self = shift;
56             my $scheme = $Self->{scheme} // 'http';
57             my $host = $Self->{host} // 'localhost';
58             my $port = $Self->{port} // ($scheme eq 'http' ? 80 : 443);
59             my $url = $Self->{uploadurl} // '/jmap/upload/';
60              
61             return $url if $url =~ m/^http/;
62              
63             return "$scheme://$host:$port$url";
64             }
65              
66             sub downloaduri {
67             my $Self = shift;
68             my ($accountId, $blobId, $name) = @_;
69             die "need account and blob" unless ($accountId and $blobId);
70             $name ||= "download";
71             my $scheme = $Self->{scheme} // 'http';
72             my $host = $Self->{host} // 'localhost';
73             my $port = $Self->{port} // ($scheme eq 'http' ? 80 : 443);
74             my $url = $Self->{downloadurl} // '/jmap/download/{accountId}/{blobId}/{name}';
75              
76             my %map = (
77             accountId => $accountId,
78             blobId => $blobId,
79             name => $name,
80             );
81              
82             $url =~ s/\{([a-zA-Z0-9_]+)\}/$map{$1}||''/ges;
83              
84             return $url if $url =~ m/^http/;
85              
86             return "$scheme://$host:$port$url";
87             }
88              
89             sub uri {
90             my $Self = shift;
91             my $scheme = $Self->{scheme} // 'http';
92             my $host = $Self->{host} // 'localhost';
93             my $port = $Self->{port} // ($scheme eq 'http' ? 80 : 443);
94             my $url = $Self->{url} // '/jmap/';
95              
96             return $url if $url =~ m/^http/;
97              
98             return "$scheme://$host:$port$url";
99             }
100              
101             sub AuthRequest {
102             my ($Self, $Requests, %Headers) = @_;
103              
104             $Headers{'Content-Type'} //= "application/json";
105             $Headers{'Accept'} //= "application/json";
106              
107             my $uri = $Self->authuri();
108              
109             my $Response = $Self->ua->post($uri, {
110             headers => \%Headers,
111             content => encode_json($Requests),
112             });
113              
114             my $jdata;
115             $jdata = eval { decode_json($Response->{content}) } if $Response->{success};
116              
117             if ($ENV{DEBUGJMAP}) {
118             warn "JMAP " . Dumper($Requests, $Response);
119             }
120              
121             # check your own success on the Response object
122             if (wantarray) {
123             return ($Response, $jdata);
124             }
125              
126             confess "JMAP request for $Self->{user} failed ($uri): $Response->{status} $Response->{reason}: $Response->{content}"
127             unless $Response->{success};
128              
129             confess "INVALID JSON $Response->{content}" unless $jdata;
130              
131             return $jdata;
132             }
133              
134             sub Login {
135             my ($Self, $Username, $Password) = @_;
136              
137             my $data = $Self->AuthRequest({
138             username => $Username,
139             clientName => $CLIENT,
140             clientVersion => $VERSION,
141             deviceName => $Self->{deviceName} || 'api',
142             });
143              
144             while ($data->{loginId}) {
145             die "Unknown method" unless grep { $_->{type} eq 'password' } @{$data->{methods}};
146             $data = $Self->AuthRequest({
147             loginId => $data->{loginId},
148             type => 'password',
149             password => $Password,
150             });
151             }
152              
153             die "Failed to get a token" unless $data->{accessToken};
154              
155             $Self->{token} = $data->{accessToken};
156             $Self->{url} = $data->{apiUrl};
157             $Self->{upload} = $data->{upload};
158             $Self->{eventSource} = $data->{eventSource};
159              
160             return 1;
161             }
162              
163             sub Request {
164             my ($Self, $Requests, %Headers) = @_;
165              
166             $Headers{'Content-Type'} //= "application/json";
167              
168             if ($Self->{user}) {
169             $Headers{'Authorization'} = $Self->auth_header();
170             }
171             if ($Self->{token}) {
172             $Headers{'Authorization'} = "Bearer $Self->{token}";
173             }
174              
175             my $uri = $Self->uri();
176              
177             my $Response = $Self->ua->post($uri, {
178             headers => \%Headers,
179             content => encode_json($Requests),
180             });
181              
182             my $jdata;
183             $jdata = eval { decode_json($Response->{content}) } if $Response->{success};
184              
185             if ($ENV{DEBUGJMAP}) {
186             warn "JMAP " . Dumper($Requests, $Response);
187             }
188              
189             # check your own success on the Response object
190             if (wantarray) {
191             return ($Response, $jdata);
192             }
193              
194             confess "JMAP request for $Self->{user} failed ($uri): $Response->{status} $Response->{reason}: $Response->{content}"
195             unless $Response->{success};
196              
197             confess "INVALID JSON $Response->{content}" unless $jdata;
198              
199             return $jdata;
200             }
201              
202             sub _get_type {
203             my $data = shift;
204             # XXX - escape file names?
205             my $magic = File::LibMagic->new();
206             my $info = $magic->info_from_string($data);
207             return $info->{mime_type};
208             }
209              
210             sub Upload {
211             my ($Self, $data, $type) = @_;
212              
213             my %Headers;
214             $Headers{'Content-Type'} = $type || _get_type($data);
215              
216             if ($Self->{user}) {
217             $Headers{'Authorization'} = $Self->auth_header();
218             }
219             if ($Self->{token}) {
220             $Headers{'Authorization'} = "Bearer $Self->{token}";
221             }
222              
223             my $uri = $Self->uploaduri();
224              
225             my $Response = $Self->ua->post($uri, {
226             headers => \%Headers,
227             content => $data,
228             });
229              
230             if ($ENV{DEBUGJMAP}) {
231             warn "JMAP UPLOAD " . Dumper($Response);
232             }
233              
234             my $jdata;
235             $jdata = eval { decode_json($Response->{content}) } if $Response->{success};
236              
237             # check your own success on the Response object
238             if (wantarray) {
239             return ($Response, $jdata);
240             }
241              
242             confess "JMAP request for $Self->{user} failed ($uri): $Response->{status} $Response->{reason}: $Response->{content}"
243             unless $Response->{success};
244              
245             confess "INVALID JSON $Response->{content}" unless $jdata;
246              
247             return $jdata;
248             }
249              
250             sub Download {
251             my $Self = shift;
252             my $cb;
253             if (ref($_[0]) eq 'CODE') {
254             $cb = shift;
255             }
256             my $uri = $Self->downloaduri(@_);
257              
258             my %Headers;
259             if ($Self->{user}) {
260             $Headers{'Authorization'} = $Self->auth_header();
261             }
262             if ($Self->{token}) {
263             $Headers{'Authorization'} = "Bearer $Self->{token}";
264             }
265              
266             my %getopts = (headers => \%Headers);
267             $getopts{data_callback} = $cb if $cb;
268             my $Response = $Self->ua->get($uri, \%getopts);
269              
270             if ($ENV{DEBUGJMAP}) {
271             warn "JMAP DOWNLOAD @_ " . Dumper($Response);
272             }
273              
274             die "Failed to download $uri" unless $Response->{success};
275             return $Response;
276             }
277              
278             1;
279             __END__