File Coverage

lib/Mailru/Cloud.pm
Criterion Covered Total %
statement 38 181 20.9
branch 0 42 0.0
condition 0 22 0.0
subroutine 13 24 54.1
pod 7 8 87.5
total 58 277 20.9


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