6 use File
::Temp qw
/ tempfile tempdir /;
20 while ($ARGV[$argo] =~ /^-/) {
21 $dl_only = 1 if ($ARGV[$argo] =~ /o/);
22 $secure = 1 if ($ARGV[$argo] =~ /s/);
23 $secure = 0 if ($ARGV[$argo] =~ /S/);
29 my ($host, $port, $selector) = ng_parse_uri
($ARGV[$argo]);
32 my $shortname = fileparse
($selector);
33 download_aux
($host, $port, $selector, $shortname);
34 print "\nSaved (?) file $shortname\n";
38 my $cui = create_ui
();
45 sub ngo_next_selector
()
47 my $last_id = length(@history) - 1;
48 my $last = $history[ $last_id ];
50 ng_advance
($item{'host'}, $item{'port'}, $item{'selector'}, $item{'data'});
52 sub ngo_queue_selector
54 my ($host, $port, $selector, $post) = @_;
56 'uri' => 'gopher://'.$host.':'.$port.'/'.$selector,
57 'selector' => $selector,
66 my $cui = new Curses
::UI
( -color_support
=> 1 );
71 { -label
=> 'Go To... g', -value
=> \
&goto_dialog
},
72 { -label
=> 'Exit ^C', -value
=> \
&exit_dialog
},
77 { -label
=> 'Download... d', -value
=> \
&download_dialog
},
78 { -label
=> 'Follow f', -value
=> \
&follow_cb
},
79 #{ -label => 'Go Back b', -value => 0 },
90 $cui->set_binding(sub {$menu->focus}, "\cX");
91 $cui->set_binding(sub {$cui->layout}, "\cR");
92 $cui->set_binding( \
&exit_dialog
, "\cC");
93 $cui->set_binding( \
&exit_dialog
, "q");
94 $cui->set_binding( \
&goto_dialog
, "g");
95 $cui->set_binding( \
&follow_cb
, "f");
96 $cui->set_binding( \
&download_dialog
, "d");
106 my $win2 = $cui->add(
114 my $statusbar = $win2->add(
118 -text
=> 'Ready. Use ^X to access Menu.',
130 my $win = $cui->getobj('win1');
132 #my $curlen = $label->width - length($_[0]) - 2;
133 #if ($curlen < 0) { $curlen = 0; }
134 #$label->text($_[0].''.sprintf('%'.$curlen.'s', ''));
141 my $win = $cui->getobj('win2');
142 my $label = $win->getobj('label');
143 my $curlen = $label->width - length($_[0]) - 2;
144 if ($curlen < 0) { $curlen = 0; }
145 $label->text($_[0].''.sprintf('%'.$curlen.'s', ''));
153 #$label->intellidraw;
158 my $return = $cui->dialog(
159 -message
=> "Do you really want to quit?",
161 -buttons
=> ['yes', 'no'],
168 my $return = $cui->question(
169 -question
=> "'New Gopher' URI:",
170 -answer
=> $host.':'.$port.'/'.$selector,
171 #-title => "Are you sure???",
173 my ($host, $port, $selector) = ng_parse_uri
($return);
175 ng_advance
($host, $port, $selector);
176 #ngo_queue_selector($host, $port, $selector);
177 #$cui->schedule_event( \&ngo_next_selector );
183 my ($host, $port, $selector, $post) = @_;
185 statusbar
("Getting $host:$port/$selector");
187 my $resp = gopher_grab
($host, $port, $selector, $post);
189 if (!$resp) { return; }
191 my ($size, $mimetype, $data) = ng_parse_response
($resp);
194 statusbar
("$selector [$mimetype] $size b");
196 if ($mimetype eq 'text/x-menu')
199 gophmenu_dialog
($size, $mimetype, $data);
201 elsif ($mimetype eq 'application/x-interactive')
203 handle_interactive
($host, $port, $selector, $data);
205 elsif ($mimetype =~ /^text\//)
207 if (!$data) { $data = '<no data>'; }
208 my $title = $selector;
209 $title =~ s/\t.*//; # hide metadata
210 $cui->dialog(-message
=> $data, -title
=> $title, -bfg
=> "red");
214 $cui->dialog('File type ['.$mimetype.'] size '.$size.' should be downloaded'."\n".substr($data, 0, 1024));
217 statusbar
("$selector [$mimetype] $size b");
221 sub gophmenu_dialog
{
222 my ($size, $type, $data) = @_;
224 my @menu = ng_parse_menu
($data, $host, $port);
227 my (@values, %labels, $i);
232 $labels{$i} = gophmenu_pretty
($item{'type'}, $item{'mimetype'}, $item{'name'}, $item{'selector'});
236 my $win = $cui->getobj("win1");
237 my $lb = $win->getobj("mylistbox");
239 $lb->values( \
@values );
240 $lb->labels( \
%labels );
241 $lb->userdata( \
@menu );
245 my $listbox = $win->add(
246 'mylistbox', 'Listbox',
250 -onchange
=> \
&gophmenu_select
,
251 -onselchange
=> \
&gophmenu_hover
,
259 my @udata = @
{$listbox->userdata()};
260 my %item = %{$udata[ $listbox->get_active_id() ]};
266 my %item = gophmenu_getitem
@_;
268 statusbar
($item{'host'}.':'.$item{'port'}.'/'.$item{'selector'});
274 my %item = gophmenu_getitem
$listbox;
276 $listbox->clear_selection();
278 ng_advance
($item{'host'}, $item{'port'}, $item{'selector'});
279 #ngo_queue_selector($item{'host'}, $item{'port'}, $item{'selector'});
280 #$cui->schedule_event( \&ngo_next_selector );
284 my $list = $cui->getobj("win1")->getobj("mylistbox");
285 gophmenu_select
($list);
288 sub download_dialog
() {
289 my $list = $cui->getobj("win1")->getobj("mylistbox");
290 my %item = gophmenu_getitem
$list;
292 if ($item{'type'} ne 'b') {
293 statusbar
("Can't download non-binary selector ".$item{'selector'});
297 my $shortname = fileparse
($item{'selector'});
298 my $file = $cui->savefilebrowser(-file
=> $shortname);
300 download_aux
($item{'host'}, $item{'port'}, $item{'selector'}, $file);
302 statusbar
("Saved file ".$file);
307 my ($type, $mimetype, $name, $selector) = @_;
309 # return sprintf(" -- %s", $name);
312 #$hint = $mimetype if $mimetype;
313 $hint = 'TXT' if $mimetype =~ /text/;
314 $hint = 'IMG' if $mimetype =~ /image/;
315 $hint = 'DIR' if $type eq 'm';
316 $hint = '==>' if $type eq 's';
318 return sprintf("%s %4s %s", $type, $hint, $name);
322 my ($host, $port, $selector, $post) = @_;
323 statusbar
("Connecting to $host : $port\n");
324 my $sock = $secure ? new IO
::Socket
::SSL
(PeerAddr
=> $host, PeerPort
=> $port, Proto
=> 'tcp', )
325 : new IO
::Socket
::INET
(PeerAddr
=> $host, PeerPort
=> $port, Proto
=> 'tcp', Timeout
=> 15, );
326 if (!$sock) { show_error
("Can't connect to $host : $port"); return ""; }
327 #return "Could not create socket: $!\n" unless $sock;
328 statusbar
("Requesting '$selector'\n");
329 print $sock $selector."\r\n";
331 print $sock length($post) . "\t\r\n";
335 my $header = <$sock>;
336 # TODO: Must add some kind of time-out here
337 if (!($header =~ /(-1|\d+)\t(.*)/)) {
338 show_error
("Can't parse header: (".length($header).")\n".$header."\n");
341 my ($size, $mimetype) = ($1, $2);
342 my ($len, $n, $need) = (0, 0, 1024);
343 statusbar
("Receiving selector '$selector' size $size, $mimetype\n");
347 if ($size != -1 && $size - $len < $need) { $need = $size - $len; }
348 statusbar
("Waiting for $need more");
349 $n = read($sock, $data, $need);
350 statusbar
("+$n Read $len of $size");
351 if (!defined $n) { next; }
353 if ($len >= $size) { last; }
354 if (eof $sock) { last; }
359 # $cui->dialog("GOT:\n".$data);
360 return $header . $data;
363 sub handle_interactive
() {
365 my ($host, $port, $selector, $data) = @_;
366 my ($long_target, $rest) = split /\n/, $data, 2;
367 my $target = substr $long_target, 1;
370 my ($fh, $filename) = tempfile
();
374 my $sb = stat($filename);
375 my $mtime = $sb->mtime;
378 system('$EDITOR '.$filename);
381 my $sb = stat($filename);
382 if ($mtime != $sb->mtime) {
383 my $fh; open $fh, $filename;
386 while (<$fh>) { $buf .= $_; }
389 ng_advance
($host, $port, $target, $buf);
390 #ngo_queue_selector($host, $port, $target, $buf);
391 #$cui->schedule_event( \&ngo_next_selector );
393 statusbar
("Interactive input aborted");
399 $cui->dialog(-message
=> $_[0], -title
=> "Error", -fg
=> "blue");
410 print " ./ngo [OPTIONS] HOST[:PORT][/SELECTOR] \n";
411 print " Options: \n";
412 print " -o Download file in non-interactive mode.\n";
413 print " -S Use plaintext connection istead of TLS.\n";
414 print " -s Use secure TLS connection.\n";
415 #print " -p Post a Form file FILE.\n";
419 my ($host, $port, $selector, $filename) = @_;
421 my $resp = gopher_grab
($host, $port, $selector);
422 if (!$resp) { return; }
424 my ($size, $mimetype, $data) = ng_parse_response
($resp);
426 open my $fh, '>'.$filename
427 || die "Unable to open $filename for writing";