make getpeername() return the original socket address which before it was intercepted
[hband-tools.git] / urxvt-extensions / perl / inbandfiletransfer-osc
bloba1a8014e8cff2c34c65f9f2ecfb134f1844e7b88
1 #! perl
3 use Data::Dumper;
4 use MIME::Base64 qw/decode_base64 encode_base64 encoded_base64_length/;
5 use Digest::MD5 qw/md5_hex/;
6 use Gtk2 qw/-init -threads-init/;
7 use threads;
8 use Glib qw/TRUE FALSE/; 
9 use Number::Bytes::Human qw/format_bytes/;
10 use Time::HiRes qw/gettimeofday/;
11 use constant {STAT_DEVNO=>0, STAT_INO=>1, STAT_MODE=>2, STAT_LINKS=>3, STAT_UID=>4, STAT_GID=>5, STAT_DEVIDENT=>6, STAT_SIZE=>7, STAT_ATIME=>8, STAT_MTIME=>9, STAT_CTIME=>10, STAT_PREFBLKSZ=>11, STAT_BLOCKS=>12};
12 use constant {RXVT_CAPTURE=>1, RXVT_PASS=>()};
14 my $is_download_in_progress = 0;
15 my $is_upload_in_progress = 0;
16 my $actual_filename;
17 my $expect_filesize;
18 my $file_handle;
19 my $data_encoding;
20 my $digest_handle;
21 my $buffer = '';
22 my $last_used_folder = '.';
23 my $pbar_updated_time = 0;
24 my $chunk_size = 72;
25 my $chunk_sent;
26 my $Conv = {
27         'base64' => {
28                 'decode' => sub{ return decode_base64($_[0]); },
29                 'closeline' => '^=====',
30         },
31         'uucp' => {
32                 'decode' => sub{ },
33                 'closeline' => '',
34         },
35         'QP' => {
36                 'decode' => sub{ },
37                 'closeline' => '',
38         },
41 my $gui = {
42         'wind' => Gtk2::Window->new,
43         'box1' => Gtk2::VBox->new,
44         'box3' => Gtk2::HBox->new,
45         'pbar' => Gtk2::ProgressBar->new,
46         'lab1' => Gtk2::Label->new,
48         'ibtn' => Gtk2::Button->new_with_label(" I "),
49         'iwnd' => Gtk2::Window->new,
50         'scrl' => Gtk2::ScrolledWindow->new,
51         'info' => Gtk2::TextView->new,
53 $gui->{'wind'}->add($gui->{'box1'});
54 $gui->{'wind'}->set_title("rxvt In-Band File Transfer");
55 $gui->{'wind'}->set_default_size(400, 50);
56 $gui->{'wind'}->signal_connect('delete_event' => \&hide_win);
57 $gui->{'lab1'}->set_alignment(0, 0.5);
58 $gui->{'lab1'}->set_justify('left');
59 $gui->{'ibtn'}->signal_connect('clicked' => \&show_log_win);
60 $gui->{'box1'}->pack_start($gui->{'pbar'}, TRUE, FALSE, 0);
61 $gui->{'box1'}->pack_start($gui->{'box3'}, TRUE, TRUE, 0);
62 $gui->{'box3'}->pack_start($gui->{'lab1'}, TRUE, TRUE, 0);
63 $gui->{'box3'}->pack_start($gui->{'ibtn'}, FALSE, FALSE, 0);
65 $gui->{'iwnd'}->add($gui->{'scrl'});
66 $gui->{'iwnd'}->set_title("rxvt In-Band File Transfer - Log");
67 $gui->{'iwnd'}->set_default_size(370, 450);
68 $gui->{'iwnd'}->signal_connect('delete_event' => \&hide_win);
69 $gui->{'scrl'}->set_border_width(5);
70 $gui->{'scrl'}->set_policy('automatic', 'automatic');
71 $gui->{'scrl'}->add($gui->{'info'});
72 $gui->{'info'}->set_editable(FALSE);
73 my $g_thread = threads->new(\&rungui);
77 sub on_osc_seq_perl
79    my ($rxvt, $osc, $resp) = @_;
81    if($osc =~ /^inbandfiletransfer;(.+)/)
82    {
83       my ($action, $encoding, $filename, $size) = split /;/, $1;
84       
85           if(grep {$encoding eq $_} keys %$Conv)
86       {
87         $data_encoding = $encoding;
89         if($action eq 'download')
90         {
91                 $filename =~ s{.*/}{};
92                 
93                         gui_lock();
94                 my $file_chooser = Gtk2::FileChooserDialog->new("Download", undef, 'save', 'gtk-cancel'=>'cancel', 'gtk-save'=>'ok');
95                 $file_chooser->set_do_overwrite_confirmation(TRUE);
96                         $file_chooser->set_current_folder($last_used_folder);
97                         $file_chooser->set_current_name($filename);
98                         my $response = $file_chooser->run();
99                         if($response eq 'ok')
100                         {
101                                 $filename = $file_chooser->get_filename();
102                                 $last_used_folder = $file_chooser->get_current_folder();
103                         }
104                         $file_chooser->destroy();
105                         gui_unlock();
107                         if($response eq 'ok')
108                         {
109                                 $rxvt->tt_write('y');
110                         $actual_filename = $filename;
111                         $expect_filesize = $size || -1;
112                         add_log($actual_filename);
113                         if(open $file_handle, '>', $actual_filename)
114                         {
115                                 $is_download_in_progress = 1;
116                                 #$rxvt->enable('add_lines');
117                                 $digest_handle = Digest::MD5->new;
118                                 $buffer = '';
119                                 message("Download started");
120                                 zero_pbar();
121                         }
122                         else
123                         {
124                                 error("File open error: $!");
125                         }
126                         }
127                         else
128                         {
129                                 $rxvt->tt_write('n');
130                         }
131         }
132         elsif($action eq 'upload')
133         {
134                         gui_lock();
135                 my $file_chooser = Gtk2::FileChooserDialog->new("Upload", undef, 'open', 'gtk-cancel'=>'cancel', 'gtk-open'=>'ok');
136                         $file_chooser->set_current_folder($last_used_folder);
137                         my $response = $file_chooser->run();
138                         if($response eq 'ok')
139                         {
140                                 $filename = $file_chooser->get_filename();
141                                 $last_used_folder = $file_chooser->get_current_folder();
142                         }
143                         $file_chooser->destroy();
144                         gui_unlock();
146                         if($response eq 'ok')
147                         {
148                                 $rxvt->tt_write('y');
149                                 add_log($filename);
150                                 if(open($file_handle, '<', $filename))
151                                 {
152                                 $digest_handle = Digest::MD5->new;
153                                         $expect_filesize = (stat $filename)[STAT_SIZE];
154                                         
155                                         $is_upload_in_progress = 1;
156                                 #$rxvt->enable('add_lines');
157                                         $rxvt->tt_write($filename . "\n");
158                                         message("Upload started");
159                                         zero_pbar();
160                                         send_a_file_chunk($rxvt);
161                                 }
162                                 else
163                                 {
164                                         error("File open error: $!");
165                                 }
166                         }
167                         else
168                         {
169                                 $rxvt->tt_write('n');
170                         }
171             }
172         else
173         {
174                 error("Unknown action: $action");
175         }
176       }
177       else
178       {
179         error("Unknown encoding: $encoding");
180       }
181           return RXVT_CAPTURE;
182    }
183    return RXVT_PASS;
186 sub on_add_lines
188         my ($rxvt, $data) = @_;
189         if($is_download_in_progress)
190         {
191                 $data = $buffer . $data;
192                 my $closeline = $Conv->{$data_encoding}->{'closeline'};
193                 while($data =~ s/(.*\n)//)
194                 {
195                         my $line = $1;
196                         if($line =~ /$closeline/)
197                         {
198                                 $is_download_in_progress = 0;
199                         #$rxvt->disable('add_lines');
200                                 my $size = tell $file_handle;
201                                 my $md5 = uc $digest_handle->hexdigest;
202                                 gui_lock();
203                                 $gui->{'pbar'}->set_fraction(1);
204                                 $gui->{'pbar'}->set_text(sprintf "%sB (100%%)", size4human($size));
205                                 gui_unlock();
206                                 add_log("MD5 $md5");
208                                 if(close $file_handle)
209                                 {
210                                         message("Downloaded");
211                                 }
212                                 else
213                                 {
214                                         error("File close error: $!");
215                                 }
217                                 $rxvt->scr_add_lines($data);
218                                 return RXVT_CAPTURE;
219                         }
220                         else
221                         {
222                                 if(defined $file_handle)
223                                 {
224                                         my $filedata = $Conv->{$data_encoding}->{'decode'}->($line);
225                                         my $part_size = tell($file_handle) + length($filedata);
227                                         gui_lock();
228                                         show_main_win();
229                                         if($pbar_updated_time + 0.5 < gettimeofday())
230                                         {
231                                                 if($expect_filesize >= 0)
232                                                 {
233                                                         my $prc = $part_size / $expect_filesize;
234                                                         $gui->{'pbar'}->set_fraction($prc);
235                                                         $gui->{'pbar'}->set_text(sprintf "%sB (%d%%)", size4human($part_size), $prc*100);
236                                                 }
237                                                 else
238                                                 {
239                                                         $gui->{'pbar'}->pulse;
240                                                         $gui->{'pbar'}->set_text(sprintf "%sB", size4human($part_size));
241                                                 }
242                                                 $pbar_updated_time = gettimeofday();
243                                         }
244                                         gui_unlock();
245                                         
246                                         $digest_handle->add($filedata);
247                                         my $ok = print {$file_handle} $filedata;
248                                         if(not $ok)
249                                         {
250                                                 error("File write error: $!");
251                                                 close $file_handle;
252                                                 undef $file_handle;
253                                         }
254                                 }
255                         }
256                 }
257                 $buffer = $data;
258                 return RXVT_CAPTURE;
259         }
260         elsif($is_upload_in_progress)
261         {
262                 $data = $buffer . $data;
263                 while($data =~ s/(.*\n)//)
264                 {
265                         my $line = $1;
266                         if($line =~ /^=(.+)$/)
267                         {
268                                 my $msg = $1;
269                                 $msg =~ s/\s*$//;
270                                 if($msg eq 'received')
271                                 {
272                                         my $sent_size = tell $file_handle;
273                                         my $prc = $sent_size / $expect_filesize;
274                                         gui_lock();
275                                         $gui->{'pbar'}->set_fraction($prc);
276                                         $gui->{'pbar'}->set_text(sprintf "%sB (%d%%)", size4human($sent_size), $prc*100);
277                                         gui_unlock();
278                                         
279                                         my $now = time;
280                                         if($now < $chunk_sent + 1.0)
281                                         {
282                                                 $chunk_size *= 2;
283                                         }
284                                         elsif($now > $chunk_sent + 1.8)
285                                         {
286                                                 $chunk_size = int($chunk_size / 2);
287                                         }
288                                         send_a_file_chunk($rxvt);
289                                 }
290                                 else
291                                 {
292                                         print STDERR Dumper $msg;
293                                 }
294                         }
295                         else
296                         {
297                                 $is_upload_in_progress = 0;
298                                 #$rxvt->disable('add_lines');
299                                 close $file_handle;
300                                 undef $file_handle;
301                                 error("Upload is interruped");
302                                 last;
303                         }
304                 }
305                 $buffer = $data;
306                 return RXVT_CAPTURE;
307         }
308         return RXVT_PASS;
311 sub on_destroy
313         Gtk2->main_quit;
314         $g_thread->join;
315         0;
318 sub send_a_file_chunk
320         if(defined $file_handle)
321         {
322                 my ($rxvt) = @_;
323                 my $buf;
324                 if(read($file_handle, $buf, $chunk_size*57))
325                 {
326                         $digest_handle->add($buf);
327                         $rxvt->tt_write(encoded_base64_length($buf) . "\n");
328                         $rxvt->tt_write(encode_base64($buf));
329                         $chunk_sent = time;
330                 }
331                 else
332                 {
333                         $is_upload_in_progress = 0;
334                         #$rxvt->disable('add_lines');
335                         my $md5 = uc $digest_handle->hexdigest;
336                         add_log("MD5 $md5");
337         
338                         if(eof($file_handle))
339                         {
340                                 $rxvt->tt_write("0\n");
341                                 #$rxvt->tt_write(chr(0x04));
342                                 message("Uploaded");
343                         }
344                         else
345                         {
346                                 $rxvt->tt_write("\n");
347                                 error("File read error: $!");
348                         }
349                         close $file_handle;
350                         undef $file_handle;
351                 }
352         }
355 sub error
357         add_log($_[0]);
358         upd_status('<span foreground="darkred"><b>'.$_[0].'</b></span>');
361 sub message
363         add_log($_[0]);
364         upd_status($_[0]);
367 sub upd_status
369         gui_lock();
370         show_main_win();
371         $gui->{'lab1'}->set_markup($_[0]);
372         gui_unlock();
375 sub add_log
377         gui_lock();
378         my $buffer = $gui->{'info'}->get_buffer;
379         my (undef, $end) = $buffer->get_bounds;
380         $buffer->insert($end, $_[0] . "\n");
381         gui_unlock();
384 sub show_main_win
386         $gui->{'wind'}->show_all;
389 sub show_log_win
391         $gui->{'iwnd'}->show_all;
394 sub hide_win
396         $_[0]->hide;
397         return Gtk2::EVENT_STOP;
400 sub guido
402         gui_lock();
403         my $o = shift @_;
404         my $m = shift @_;
405         $gui->{$o}->$m(@_);
406         gui_unlock();
409 sub gui_lock
411         Gtk2::Gdk::Threads->enter;
414 sub gui_unlock
416         Gtk2::Gdk::Threads->leave;
419 sub rungtk
421         Gtk2->main_iteration while Gtk2->events_pending;
424 sub rungui
426         Gtk2->main;
429 sub zero_pbar
431         gui_lock();
432         $gui->{'pbar'}->set_fraction(0);
433         $gui->{'pbar'}->set_text("(0%)");
434         gui_unlock();
437 sub size4human
439         my ($n, $u) = format_bytes(int $_[0]) =~ /([\d\.,]+)(.*)/;
440         return "$n $u";