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   54592 use strict;
  1         2  
  1         27  
4 1     1   4 use warnings;
  1         1  
  1         50  
5              
6             package Mail::JMAPTalk;
7              
8 1     1   554 use HTTP::Tiny;
  1         40603  
  1         63  
9 1     1   718 use JSON::XS qw(decode_json encode_json);
  1         5310  
  1         90  
10 1     1   356 use Convert::Base64;
  1         1031  
  1         48  
11 1     1   163 use File::LibMagic;
  0            
  0            
12             use Carp qw(confess);
13             use Data::Dumper;
14              
15             our $VERSION = '0.08';
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, $accountId) = @_;
212              
213             my %Headers;
214             $Headers{'Content-Type'} = $type || _get_type($data);
215              
216             if (defined $accountId) {
217             $Headers{'X-JMAP-AccountId'} = $accountId;
218             }
219             if ($Self->{user}) {
220             $Headers{'Authorization'} = $Self->auth_header();
221             }
222             if ($Self->{token}) {
223             $Headers{'Authorization'} = "Bearer $Self->{token}";
224             }
225              
226             my $uri = $Self->uploaduri();
227              
228             my $Response = $Self->ua->post($uri, {
229             headers => \%Headers,
230             content => $data,
231             });
232              
233             if ($ENV{DEBUGJMAP}) {
234             warn "JMAP UPLOAD " . Dumper($Response);
235             }
236              
237             my $jdata;
238             $jdata = eval { decode_json($Response->{content}) } if $Response->{success};
239              
240             # check your own success on the Response object
241             if (wantarray) {
242             return ($Response, $jdata);
243             }
244              
245             confess "JMAP request for $Self->{user} failed ($uri): $Response->{status} $Response->{reason}: $Response->{content}"
246             unless $Response->{success};
247              
248             confess "INVALID JSON $Response->{content}" unless $jdata;
249              
250             return $jdata;
251             }
252              
253             sub Download {
254             my $Self = shift;
255             my $cb;
256             if (ref($_[0]) eq 'CODE') {
257             $cb = shift;
258             }
259             my $uri = $Self->downloaduri(@_);
260              
261             my %Headers;
262             if ($Self->{user}) {
263             $Headers{'Authorization'} = $Self->auth_header();
264             }
265             if ($Self->{token}) {
266             $Headers{'Authorization'} = "Bearer $Self->{token}";
267             }
268              
269             my %getopts = (headers => \%Headers);
270             $getopts{data_callback} = $cb if $cb;
271             my $Response = $Self->ua->get($uri, \%getopts);
272              
273             if ($ENV{DEBUGJMAP}) {
274             warn "JMAP DOWNLOAD @_ " . Dumper($Response);
275             }
276              
277             die "Failed to download $uri" unless $Response->{success};
278             return $Response;
279             }
280              
281             1;
282             __END__