File Coverage

lib/Mailru/Cloud.pm
Criterion Covered Total %
statement 163 175 93.1
branch 24 40 60.0
condition 9 22 40.9
subroutine 24 24 100.0
pod 7 8 87.5
total 227 269 84.3


line stmt bran cond sub pod time code
1             package Mailru::Cloud;
2              
3 1     1   1245 use 5.008001;
  1         4  
4 1     1   5 use strict;
  1         2  
  1         19  
5 1     1   5 use warnings;
  1         2  
  1         22  
6 1     1   17 use utf8;
  1         2  
  1         7  
7 1     1   30 use open qw(:std :utf8);
  1         1  
  1         6  
8 1     1   167 use Carp qw/croak carp/;
  1         2  
  1         48  
9 1     1   5 use URI::Escape;
  1         1  
  1         65  
10 1     1   7 use File::Basename;
  1         2  
  1         71  
11 1     1   7 use HTTP::Request;
  1         3  
  1         27  
12 1     1   12 use JSON::XS;
  1         2  
  1         54  
13 1     1   573 use Encode;
  1         11246  
  1         79  
14 1     1   855 use IO::Socket::SSL;
  1         88673  
  1         10  
15 1     1   181 use base qw/Mailru::Cloud::Auth/;
  1         3  
  1         2455  
16              
17             our $VERSION = '0.08';
18              
19             my $BUFF_SIZE = 512;
20              
21             sub uploadFile {
22 2     2 1 1364 my ($self, %opt) = @_;
23 2   33     19 my $upload_file = $opt{'-file'} || croak "You must specify -file param for method uploadFile";
24 2   50     10 my $path = $opt{'-path'} || '/';
25 2         4 my $rename = $opt{'-rename'};
26 2         8 $self->{file_hash} = undef;
27              
28 2         14 $self->__isLogin();
29              
30 2 100       11 my $conflict_mode = $rename ? 'rename' : 'rewrite';
31              
32 2 50       102 if (not -f $upload_file) {
33 0         0 croak "File $upload_file not exist";
34             }
35              
36 2 50       19 if ($path !~ /\/$/) {
37 0         0 $path .= '/';
38             }
39              
40 2         21 my $request = 'https://cld-upload10.cloud.mail.ru/upload/?' .'cloud_domain=2&x-email=' . uri_escape($self->{email});
41              
42 2 50       82 my ($file_hash, $size) = $self->__upload_file($request, $upload_file) or return;
43 2         360 $self->{file_hash} = $file_hash;
44              
45             #Опубликуем файл
46             my %param = (
47             'api' => '2',
48             'build' => $self->{build},
49             'conflict' => $conflict_mode,
50             'email' => $self->{email},
51             'hash' => $file_hash,
52             'home' => $path . basename($upload_file),
53             'size' => $size,
54             'token' => $self->{authToken},
55             'x-email' => $self->{email},
56 2         181 'x-page-id' => $self->{'x-page-id'},
57             );
58 2         27 my $res = $self->{ua}->post('https://cloud.mail.ru/api/v2/file/add', \%param);
59              
60 2         756522 my $code = $res->code;
61 2 50       47 if ($code eq '200') {
62 2         12 my $json = JSON::XS::decode_json($res->content);
63 2         62 my $new_fname = $json->{body};
64 2         73 return $new_fname;
65             }
66              
67 0         0 croak "Cant upload file $upload_file. Code: $code " . $res->decoded_content . "\n";
68             }
69              
70             sub downloadFile {
71 2     2 1 13 my ($self, %opt) = @_;
72 2   33     11 my $file = $opt{-file} || croak "You must specify -file param for method downloadFile";
73 2   33     10 my $cloud_file = $opt{-cloud_file} || croak "You must specify -cloud_file param for method downloadFile";
74              
75 2         13 $self->__isLogin();
76              
77 2         10 my $FL;
78 2         38 my $ua = $self->{ua};
79 2         9 $DB::single = 1;
80 2         14 my $url = 'https://cloclo5.datacloudmail.ru/get/' . uri_escape($cloud_file) . '?x-email=' . uri_escape($self->{email});
81             my $res = $ua->get($url, ':read_size_hint' => $BUFF_SIZE, ':content_cb' => sub {
82 1501 100   1501   1728227 if (not $FL) {
83 1 50       183 open $FL, ">$file" or croak "Cant open $file to write $!";
84 1         9 binmode $FL;
85             }
86 1501         6066 print $FL $_[0];
87 2         114 });
88 2         272942 my $code = $res->code;
89 2 100       28 if ($code ne '200') {
90 1         238 croak "Cant download file $cloud_file to $file. Code: $code";
91             }
92 1 50       43 close $FL if $FL;
93 1         91 return 1;
94             }
95              
96             sub createFolder {
97 1     1 1 5 my ($self, %opt) = @_;
98 1   33     11 my $path = $opt{-path} || croak "You must specify -path param for method createFolder";
99              
100 1         6 $self->__isLogin();
101              
102 1         6 my $ua = $self->{ua};
103             my %param = (
104             'api' => '2',
105             'build' => $self->{build},
106             'conflict' => 'strict',
107             'email' => $self->{email},
108             'home' => $path,
109             'token' => $self->{authToken},
110             'x-email' => $self->{email},
111 1         29 'x-page-id' => $self->{'x-page-id'},
112             );
113 1         25 my $res = $ua->post('https://cloud.mail.ru/api/v2/folder/add', \%param);
114              
115 1         287419 my $code = $res->code;
116 1 50       14 if ($code eq '200') {
117 1         32 return 1;
118             }
119 0 0       0 if ($code eq '400') {
120 0         0 carp "Can't create folder $path. Folder exists";
121 0         0 return;
122             }
123 0         0 croak "Cant create folder $path. Code: $code";
124              
125             }
126              
127             sub deleteResource {
128 4     4 1 1555 my ($self, %opt) = @_;
129 4   33     20 my $path = $opt{-path} || croak "You must specify -path options for method deleteResource";
130              
131 4         25 $self->__isLogin();
132              
133             my %param = (
134             'api' => '2',
135             'build' => $self->{build},
136             'email' => $self->{email},
137             'home' => $path,
138             'token' => $self->{authToken},
139             'x-email' => $self->{email},
140 4         59 'x-page-id' => $self->{'x-page-id'},
141             );
142              
143 4         27 my $res = $self->{ua}->post('https://cloud.mail.ru/api/v2/file/remove', \%param);
144 4         1191652 my $code = $res->code;
145              
146 4 50       53 if ($code eq '200') {
147 4         118 return 1;
148             }
149 0         0 croak "Cant remove $path. Code: $code";
150             }
151              
152             sub emptyTrash {
153 1     1 1 526 my $self = shift;
154              
155 1         7 $self->__isLogin();
156              
157             my %param = (
158             'api' => '2',
159             'build' => $self->{build},
160             'email' => $self->{email},
161             'token' => $self->{authToken},
162             'x-email' => $self->{email},
163 1         14 'x-page-id' => $self->{'x-page-id'},
164             );
165              
166 1         8 my $res = $self->{ua}->post('https://cloud.mail.ru/api/v2/trashbin/empty', \%param);
167 1         297927 my $code = $res->code;
168              
169 1 50       13 if ($code eq '200') {
170 1         27 return 1;
171             }
172 0         0 croak "Cant empty trash. Code: $code";
173             }
174              
175             sub listFiles {
176 3     3 1 2215 my ($self, %opt) = @_;
177 3   100     16 my $path = $opt{-path} || '/';
178 3         9 my $orig_path = $path;
179              
180 3         18 $self->__isLogin();
181 3         50 $path = uri_escape($path);
182 3         129 my $res = $self->{ua}->get('https://cloud.mail.ru/api/v2/folder' . '?token=' . $self->{authToken} . '&home=' . $path);
183 3         974343 my $code = $res->code;
184 3 100       39 if ($res->is_success) {
185 2         34 my $json_parsed = decode_json($res->content);
186 2         957 my @list_files;
187              
188 2         6 for my $item (@{$json_parsed->{body}->{list}}) {
  2         12  
189             my $h = {
190             'type' => $item->{type},
191             'name' => $item->{name},
192             'size' => $item->{size},
193 29         72 };
194 29 100       55 if ($item->{weblink}) {
195 1         5 $h->{weblink} = 'https://cloud.mail.ru/public/' . $item->{weblink};
196             }
197 29         49 push @list_files, $h;
198             }
199 2         79 return \@list_files;
200             }
201 1 50       17 if ($code eq '404') {
202 1         232 croak "Folder $orig_path not exists";
203             }
204 0         0 croak "Cant get file list for path: $orig_path. Code: $code";
205             }
206              
207             sub shareResource {
208 1     1 1 4 my ($self, %opt) = @_;
209 1   33     5 my $path = $opt{-path} || croak "You must specify -path param for method shareResource";
210              
211             #Добавим слеш в начало, если его нет
212 1         5 $path =~ s/^([^\/])/\/$1/;
213              
214             my %param = (
215             'api' => '2',
216             'build' => $self->{build},
217             'email' => $self->{email},
218             'home' => $path,
219             'token' => $self->{authToken},
220             'x-email' => $self->{email},
221 1         11 'x-page-id' => $self->{'x-page-id'},
222             );
223              
224 1         7 my $res = $self->{ua}->post('https://cloud.mail.ru/api/v2/file/publish', \%param);
225 1         297893 my $code = $res->code;
226 1 50       41 if ($code ne '200') {
227 0         0 croak "Error on shareResource. Path: $path. Code: $code";
228             }
229 1         11 my $json = decode_json($res->decoded_content);
230 1         171 my $link = 'https://cloud.mail.ru/public/' . $json->{body};
231 1         25 return $link;
232             }
233              
234             sub __upload_file {
235 2     2   10 my ($self, $url, $file) = @_;
236              
237 2         25 my $u1 = URI->new($url);
238              
239             # $IO::Socket::SSL::DEBUG = 5;
240 2         263 my $host = $u1->host;
241 2         994 my $port = $u1->port;
242 2         97 my $path = $u1->path;
243              
244 2 50       64 my $sock = IO::Socket::SSL->new(
245             PeerAddr => $host,
246             PeerPort => $port,
247             Proto => 'tcp',
248             ) or croak "Cant connect to $host:$port";
249 2         507662 binmode $sock;
250 2         30 $sock->autoflush(1);
251              
252             #Generate boundary
253 2         138 my $boundary = '5';
254 2         15 for (1..20) {
255 40         79 $boundary .= int(rand(10) + 1);
256             }
257 2         8 $boundary = '----------------------------' . $boundary;
258              
259 2         175 my $content_disposition = 'Content-Disposition: form-data; name="file"; filename="' . basename($file) . '"' . "\n";
260 2         8 $content_disposition .= "Content-Type: text/plain\n\n";
261 2         97 my $length = (stat $file)[7];
262              
263 2         10 my @cookie_arr;
264 2     16   26 $self->{ua}->cookie_jar->scan(sub {push @cookie_arr, "$_[1]=$_[2]"});
  16         389  
265 2         29 my $cookie = join('; ', @cookie_arr);
266              
267              
268 2         23 my @headers = ( "PUT $path HTTP/1.1",
269             "HOST: $host",
270             "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0",
271             "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
272             "Accept-Language: en-US,en;q=0.5",
273             "Accept-Encoding: gzip, deflate, br",
274             "Content-Type: multipart/form-data; boundary=$boundary",
275             "Connection: close",
276             "Referer: https://cloud.mail.ru/home/",
277             "Origin: https://cloud.mail.ru",
278             "Cookie: $cookie",
279             "X-Requested-With: XMLHttpRequest",
280             );
281              
282 2         6 for my $head (@headers) {
283 24         5954 $sock->print($head . "\n");
284             }
285              
286 2         364 $sock->print("Content-Length: $length\n");
287 2         365 $sock->print("\n");
288              
289              
290 2 50       470 open my $FH, "<$file" or croak "Cant open $file $!";
291 2         14 binmode $FH;
292 2         5 my $filebuf;
293 2         49 while (my $bytes = read($FH, $filebuf, $BUFF_SIZE)) {
294 3000         909179 $sock->print($filebuf);
295             }
296 2         472 $sock->print("\n");
297              
298 2         414 my @answer = $sock->getlines();
299 2         164750 $sock->close();
300              
301             #Если запрос успешен
302 2 50       1241 if ($answer[0] =~ /201/) {
303             #Возврат хэша файла
304 2         95 return (pop @answer, $length);
305             }
306              
307 0         0 return;
308             }
309              
310             ################################## ACCESSORS ##########################3
311             #
312             sub get_last_uploaded_file_hash {
313 1     1 0 1009 return $_[0]->{file_hash};
314             }
315              
316             1;
317              
318             __END__