9 # openssl s_client -connect localhost:11211
11 our $VERSION = '0.01';
14 our @EXPORT = qw(ng_parse_uri ng_parse_menu ng_parse_response);
19 my $uri = $_[0] || '';
20 $uri =~ s/gopher:\/\///;
22 my ($long_host, $selector) = split /\//, $uri;
23 my ($host, $port) = split /:/, $long_host;
24 $port = $port || 32070; # default port
26 return ($host, $port, $selector);
30 my ($data, $host, $port) = @_;
31 my @items = split /\r\n/, $data;
35 my ($mtype, $name, $selector, $_host, $_port) = split /\t/;
36 if (!$_port || !($_port =~ /\d+/)) { $_port = 0; }
39 'type' => ng_shorten_type
($mtype),
40 'mimetype' => ng_expand_type
($mtype),
41 'selector' => $selector,
42 'host' => ($_host ?
$_host : $host),
43 'port' => ($_port ?
int($_port) : $port),
50 sub ng_parse_response
{
53 my $mimetype = 'unknown';
54 if ($data =~ /(-1|\d+)\t(.+)\r/) {
59 return ($size, $mimetype, $data);
64 return 'text/x-menu' if ($type eq 'm');
65 return 'application/octet-stream' if ($type eq 'b');
71 return $type if ($type =~ /i|m|s|b|u/);
72 return 'i' if ($type eq '');
73 return 'm' if ($type eq 'text/x-menu');
74 return 's' if ($type eq 'application/x-interactive');
82 package TLSGopher
::request
;
85 my ($class, $host, $port, $selector) = @_;
91 $self->{host
} = $host;
92 $self->{port
} = $port;
93 $self->{selector
} = $selector;
96 $self->{post_fd
} = undef;
108 $self->{'buf'.'HEADER1'} = $line;
114 $self->{range
}) = split(/\t/, $line);
117 return 1 if ($line =~ /\t/);
124 my ($self, $line) = @_;
126 $self->{'buf'.'HEADER2'} = $line;
128 if (!($line =~ /^(\d+)\t{0,1}(.*)/)) {
129 $self->{errstr
} = 'Malformed request';
135 $self->{post_size
} = $1;
136 $self->{post_type
} = $2;
140 return $self->{post_size
};
144 my ($self, $data, $n) = @_;
146 if ($self->{post_fd
}) { # Connected to an output stream
148 my $fh = $self->{post_fd
};
152 $self->{'post_data'} .= $data;
154 $self->{'buf'.'SIZE'} += $n;
156 if ($self->{'buf'.'SIZE'} >= $self->{post_size
}) {
157 $self->{ready
} = $self->{ready
} || 1;
162 return $self->{sent
};
166 my ($self, $fh) = @_;
169 $self->{post_fd
} = $fh;
171 # Hack -- if we have something buffered, dump it
172 print $fh $self->{'post_data'};
173 $self->close if ($self->{sent
});
177 my ($self, $filename, $_type) = @_;
182 my $size = -s
$filename;
183 my $type = mimetype
$filename || $_type;
191 read $fh, $self->{post_data
}, $size;
194 $self->{post_size
} = $size;
195 $self->{post_type
} = $type;
199 # Large file, stream it
205 $self->{post_fd
} = $fh;
206 $self->{post_offset
} = 0;
207 $self->{post_size
} = $size;
208 $self->{post_type
} = $type;
215 my ($self, $fd, $size, $type) = @_;
217 $self->{post_fd
} = $fd;
218 $self->{post_offset
} = 0;
219 $self->{post_size
} = $size;
220 $self->{post_type
} = $type || '';
227 if (defined $self->{post_fd
}) {
228 close ($self->{post_fd
});
229 undef $self->{post_fd
};
236 $self->{post_size
} = 0;
237 $self->{post_type
} = '';
238 $self->{post_data
} = '';
242 my ($self, $data, $size, $type) = @_;
244 $self->{post_type
} = $type || $self->{post_type
};
245 $self->{post_size
} = $size;
246 $self->{post_data
} = $data;
252 my ($self, $fd) = @_;
253 $fd = select if !defined $fd;
255 my $complex = ($self->{search
} || $self->{range
} || $self->{post_fd
} || $self->{post_data
});
258 print $fd $self->{selector
};
259 print $fd $self->{search
} . "\t" . $self->{range
} if $complex;
262 # Connected to a stream
263 if ($self->{post_fd
}) {
265 print $fd $self->{post_size
} . "\t";
266 print $fd $self->{post_type
} ;
271 while (read $fd, $buf, 1024) {
276 elsif ($self->{post_data
}) {
278 print $fd $self->{post_size
} . "\t";
279 print $fd $self->{post_type
} ;
283 print $fd $self->{post_data
};
288 print $fd $self->{post_size
} . "\t";
304 package TLSGopher
::response
;
309 $self->{selector
} = '';
311 $self->{body_size
} = 0;
312 $self->{body_type
} = '';
313 $self->{body_data
} = '';
315 $self->{raw
} = 0; # No need to apply header
317 $self->{ready
} = 0; # Ready to send
318 $self->{header_sent
} = 0; # Header sent
319 $self->{sent
} = 0; # Body sent
320 $self->{is_error
} = 0;
327 my ($self, $fd) = @_;
328 $self->{body_fd
} = $fd;
336 if (@_ == 0) { return $self->{is_error
}; }
340 $menu .= $c."\t".$_."\r\n";
343 $self->{body_data
} = $menu;
344 $self->{body_type
} = 'text/x-menu';
345 $self->{body_size
} = length $menu;
347 $self->{is_error
} = 1;
351 if (@_ == 0) { return $self->{body_size
}; }
352 $self->{body_size
} = shift;
356 if (@_ == 0) { return $self->{body_type
}; }
357 $self->{body_type
} = shift;
361 for (@_) { $self->{body_data
} .= $_; }
362 $self->{body_size
} = length $self->{body_data
};
366 my ($self, $fd) = @_;
367 $fd = select if !defined $fd;
369 # Streams should handle their own errors.
371 print $fd $self->{body_size
} . "\t";
372 print $fd $self->{body_type
} ;
376 $self->{header_sent
} = 1;
380 my ($self, $fd) = @_;
381 $fd = select if !defined $fd;
384 print $fd $self->{body_data
};
386 # Connected to an input stream
387 if ($self->{body_fd
}) {
388 # Copy a portion of it
391 if (($n = read $self->{body_fd
}, $buf, 1024)) {
394 if (!$n && !$!{EAGAIN
}) {
395 #if (eof $self->{body_fd}) { # :(
398 # Hack -- clean buffered body
399 $self->{body_data
} = '';
403 # All we had was buffered body
410 my ($self, $fd) = @_;
411 # $fd = select if !defined $fd;
414 $self->print_header($fd) unless $self->{header_sent
};
417 $self->print_body($fd) unless $self->{sent
};
422 if ($self->{track_pid
} && $self->{sent
}) {
423 warn "Waiting for child process to finish :(\n";
424 waitpid $self->{track_pid
}, 0;
427 return $self->{sent
};
430 $_[0]->{raw
} = $_[1] if (defined $_[1]);
435 my ($self, $pid) = @_;
436 $self->{track_pid
} = $pid;
441 if ($self->{body_fd
}) {
442 close $self->{body_fd
};
452 package TLSGopher
::connection
;
455 HEADER1
=> 'Request',
463 my ($class, $sock) = @_;
468 $self->{sock
} = $sock;
469 $self->{binmode} = 0;
470 $self->{method
} = HEADER1
;
472 $self->{next_request
} = undef;
474 # warn "connection opened ($sock).\n";
478 # Save cert meta-data
479 my ($subject_name, $issuer_name);
480 if (ref($sock) eq "IO::Socket::SSL") {
481 $subject_name = $sock->peer_certificate("subject");
482 $issuer_name = $sock->peer_certificate("issuer");
484 $self->{TLSsubject
} = $subject_name;
485 $self->{TLSissuer
} = $issuer_name;
496 if ($self->{binmode}) {
500 if ($self->{binmode} > 0 && $self->{binmode} < $need) {
501 $need = $self->{binmode};
504 # Read binary portion
506 my $n = read $self->{sock
}, $data, $need;
507 if (!$n && !$!{EAGAIN
}) { return 0; } # Socket disconnected
512 $self->{read_cb
}($self, $data, $n);
515 # Old request, cont..
516 $self->{binmode} -= $n;
517 $self->{next_request
}->parse3($data, $n);
523 my $sock = $self->{sock
};
525 if (!$line && !$!{EAGAIN
}) { return 0; } # Socket disconnected
530 $self->{read_cb
}($self, $line, length $line);
532 if (!defined $self->{next_request
}) {
534 $self->{next_request
} = new TLSGopher
::request
;
535 $self->{next_request
}->parse1($line);
537 # New request, cont..
538 my $size = $self->{next_request
}->parse2($line);
539 $self->{binmode} = $size;
544 # React to current request
545 if (defined $self->{next_request
}) {
548 if ($self->{next_request
}->{errstr
}) {
550 $self->{sock
}->close();
553 elsif ($self->{next_request
}->{ready
} == 1) {
555 $self->handle_request( $self->{next_request
} );
556 $self->{next_request
}->{ready
} = 2; # Hack, do not repeat
558 # Request is resolved
559 if ($self->{next_request
}->{sent
}) {
561 undef $self->{next_request
} ;
565 return (defined $self->{sock
}->connected() ?
1 : 0);
572 if ($#{$self->{queue}} <= -1) { return; }
574 # Get first response in queue
575 my $resp = @
{$self->{queue
}}[0];
577 #print "Streaming $resp->{body_fd} > CLIENT\n";
580 $resp->print( $self->{sock
} );
582 # Remove it when done
584 shift (@
{$self->{queue
}});
589 my ($self, $req) = @_;
591 my $resp = new TLSGopher
::response
;
593 $req->{response
} = $resp;
594 $resp->{request
} = $req;
595 $resp->{selector
} = $req->{selector
};
597 my $stop = $self->{request_cb
}($self, $resp, $req);
599 # This allows user implementation to CANCEL response wholetogether.
600 # This is not recommended, as such behavior will break clients
601 # and a simple 0\r\n is always much better.
603 push @
{$self->{queue
}}, $resp;
610 close( $self->{sock
} );
611 # warn "\t connection closed.\n";
620 package TLSGopher
::server
;
627 # my ($class, %cfg) = @_;
629 my ($class, $cfg_ref) = @_;
630 my %cfg = %{$cfg_ref};
635 $self->{request_cb
} =
637 $self->{close_cb
} = sub { };
639 $self->{config
} = %cfg;
642 $IO::Socket
::SSL
::DEBUG
= $cfg{'Debug'} ?
1 : 0;
644 if ($cfg{'TLSoff'}) {
647 $self->{sock
} = IO
::Socket
::INET
->new(
650 LocalAddr
=> $cfg{'Host'},
651 LocalPort
=> $cfg{'Port'},
656 if (!$self->{sock
}) { $errstr = "Can't bind $cfg{'Host'} $cfg{'Port'} " }
658 $self->{sock
} = IO
::Socket
::SSL
->new(
661 LocalAddr
=> $cfg{'Host'},
662 LocalPort
=> $cfg{'Port'},
665 # SSL_verify_mode => 0x01,
666 SSL_passwd_cb
=> $cfg{'TLSpassphrase'} ?
667 sub {return $cfg{'TLSpassphrase'}} : undef,
668 SSL_key_file
=> $cfg{'TLSkey'},
669 SSL_cert_file
=> $cfg{'TLScert'},
672 if (!$self->{sock
}) { $errstr = IO
::Socket
::SSL
::errstr
; }
675 if (!$self->{sock
}) {
679 $self->{sock
}->blocking(0);
686 sub errstr
{ return $errstr; }
692 #warn "stopping server.\n";
698 #warn "waiting for connections.\n";
700 while (!$self->{stop
}) {
707 $self->{sock
}->close();
713 while((my $s = $self->{sock
}->accept())) {
715 my $conn = new TLSGopher
::connection
$s;
717 $self->{accept_cb
}($conn, $self);
719 $conn->{read_cb
} = $self->{read_cb
};
720 $conn->{request_cb
} = $self->{request_cb
};
722 push @
{$self->{conns
}}, $conn; # Add connection
725 for (my $i = 0; $i < $#{$self->{conns}} + 1; $i++) {
726 my $conn = $self->{conns
}[$i];
728 $self->{close_cb
}($@
{$self->{conns
}}[$i], $self);
729 splice @
{$self->{conns
}}, $i, 1;
737 # TODO: select on file descriptors
742 my ($self, %cfg) = @_;
744 $self->{$_.'_cb'} = $cfg{$_};
754 package TLSGopher
::client
;