App-phoebe

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

- App::Gopher is new and improved; it no longer just prints the Gemini
  text but does line wrapping and all that

3.00

- Add special feeds for the blog, i.e. the pages starting with an ISO
  date

2.08

- Updated dependencies to Mojolicious 9.0 (because the tls_verify
  parameter was replaced by a more general tls_options).

- Reduced the SSL session cache to 64 in attempt to fix a memory leak
  but which I was unable to do: the solution I'm using right now is to
  use memory parameters in the systemd service definition.

- Fixed colour links in /changes within wiki spaces.

- Fixed output of /robots.txt.

Makefile.PL  view on Meta::CPAN

    'script/spartan',
  ],
  PREREQ_PM => {
    # t/prerequisites.t is serious about all these!
    'Modern::Perl' => 1.20180701, # for '2018'
    'URI::Escape' => 0,
    'Encode::Locale' => 0,
    'Algorithm::Diff' => 0,
    'File::ReadBackwards' => 0,
    'File::Slurper' => 0,
    'Mojolicious' => 9.00,       # removed tls_verify from Mojo::IOLoop::TLS 9.0
    'IO::Socket::SSL' => 2.069,  # optional for Mojo::IOLoop
    'Net::SSLeay' => 1.90,
    'Net::IDN::Encode' => 0,
    'IRI' => 0,                  # for script/gemini
  },
  META_MERGE => {
    'meta-spec' => { version => 2 },
    resources => {
      repository => {
	type => 'git',

lib/App/Phoebe/Oddmuse.pm  view on Meta::CPAN

      . " (" . $tx->req->url->to_abs . " " . $tx->req->params . ")\r\n");
}

# If the fingerprint exists in our file, no need to ask for the $token; it
# expires after a day (24 * 60 * 60 seconds). If no fingerprint is found, ask
# for a cert.
sub oddmuse_fingerprint_name {
  my $stream = shift;
  my $host = shift;
  my $token = shift;
  # This requires SSL_verify_mode => SSL_VERIFY_PEER and SSL_verify_callback =>
  # \&verify_fingerprint (which must not reject self-signed certificates).
  my $fingerprint = $stream->handle->get_fingerprint();
  if (not $fingerprint) {
    result($stream, "60", "You need a client certificate with a common name to edit this wiki");
    return;
  }
  my $dir = $server->{wiki_dir};
  my @lines;
  my $now = time();
  # Read the known fingerprint from the file.
  my %fingerprints;

script/gemini  view on Meta::CPAN


warn "Contacting $host:$port" if $verbose;

# create client
Mojo::IOLoop->client({
  address => $host,
  port => $port,
  tls => 1,
  tls_cert => $cert,
  tls_key => $key,
  tls_options => { SSL_verify_mode => 0x00 }} => sub {
    my ($loop, $err, $stream) = @_;
    die $err if $err;
    # 1h timeout (for chat)
    $stream->timeout(3600);
    my ($header, $mimetype, $encoding);
    $stream->on(read => sub {
      my ($stream, $bytes) = @_;
      if ($header and $encoding) {
	print decode($encoding, $bytes);
      } elsif ($header) {

script/gemini-chat  view on Meta::CPAN

while (defined ($_ = $term->readline($prompt))) {
  exit if $_ eq "quit";
  # create client
  my $text = uri_escape_utf8($_);
  Mojo::IOLoop->client({
    address => $host,
    port => $port,
    tls => 1,
    tls_cert => $cert,
    tls_key => $key,
    tls_options => { SSL_verify_mode => 0x00 }} => sub {
      my ($loop, $err, $stream) = @_;
      die $err if $err;
      $stream->on(read => sub {
	my ($stream, $bytes) = @_;
	if ($bytes =~ /^[123]/) {
	  # Do nothing
	} else {
	  # Print server result
	  print "\e[31m$bytes\e[0m"; # red
	}});

script/ijirait  view on Meta::CPAN


sub stream {
  say "Use 'Ctrl+C' to quit.";
  # Start client for listening
  Mojo::IOLoop->client({
    address => $host,
    port => $port,
    tls => 1,
    tls_cert => $cert,
    tls_key => $key,
    tls_options => { SSL_verify_mode => 0x00 }} => sub {
      my ($loop, $err, $stream) = @_;
      # 1h timeout (for chat)
      $stream->timeout(3600);
      $stream->on(read => sub {
	my ($stream, $bytes) = @_;
	my $text = to_text(decode_utf8($bytes));
	print encode(locale => $text) });
      $stream->on(close => sub {
	say "Connection closed";
	exit });

script/ijirait  view on Meta::CPAN

      $command = $1;
      $shell_command = $2;
    }
    # create client
    Mojo::IOLoop->client({
      address => $host,
      port => $port,
      tls => 1,
      tls_cert => $cert,
      tls_key => $key,
      tls_options => { SSL_verify_mode => 0x00 }, } => sub {
	my ($loop, $err, $stream) = @_;
	return say $err unless $stream;
	$stream->on(read => sub {
	  my ($stream, $bytes) = @_;
	  if ($shell_command) {
	    open(my $fh, $shell_command)
		or die "Can't run $shell_command: $!";
	    $bytes =~ s/^2.*\n//; # skip header
	    print $fh $bytes;
	  } else {

script/phoebe  view on Meta::CPAN

      or die "openssl failed: $?";
  }
}

sub help {
  my $parser = Pod::Text->new();
  $parser->parse_file($0);
  exit;
}

sub verify_fingerprint {
  my ($ok, $ctx_store, $certname, $error, $cert, $depth) = @_;
  return 1;
}

# defaults
$server->{port} ||= [1965];
$server->{wiki_token} ||= ['hello'];
$server->{wiki_space} ||= [];
$server->{wiki_mime_type} ||= [];
$server->{wiki_dir} ||= $ENV{PHOEBE_DATA_DIR} || './wiki';

script/phoebe  view on Meta::CPAN

	for my $port (@{$server->{port}}) {
	  $log->info("$host: listening on $address:$port (Gemini)");
	  Mojo::IOLoop->server({
	    address => $address,
	    port => $port,
	    tls => 1,
	    tls_cert => $server->{cert_file},
	    tls_key  => $server->{key_file},
	    tls_options => {
	      # request client certificates and accept them
	      SSL_verify_mode => SSL_VERIFY_PEER,
	      SSL_verify_callback => \&verify_fingerprint,
	      SSL_create_ctx_callback => sub {
		my $ctx = shift;
		Net::SSLeay::CTX_sess_set_cache_size($ctx, 64);
	      }
	    }
	  } => sub {
	    my ($loop, $stream) = @_;
	    my $data = { buffer => '', handler => \&handle_request };
	    $stream->on(read => sub {
	      my ($stream, $bytes) = @_;

script/titan  view on Meta::CPAN

  my $data = <STDIN>;
  my $file;
  ($temp_fh, $file) = tempfile();
  print $temp_fh $data;
  close($temp_fh);
  push(@files, $file);
}

my %args = (PeerHost => $host,
	    PeerService => $port,
	    SSL_verify_mode => SSL_VERIFY_NONE);
# Default certs
$args{SSL_cert_file} = 'client-cert.pem' if -f 'client-cert.pem';
$args{SSL_key_file} = 'client-key.pem' if -f 'client-key.pem';
# Read --cert_file and --key_file
for (grep(/--(key|cert)_file=/, @ARGV)) {
  $args{SSL_cert_file} = $1 if /--cert_file=(.*)/;
  $args{SSL_key_file} = $1 if /--key_file=(.*)/;
}

for my $file (@files) {

t/00_tls_check.t  view on Meta::CPAN

}

sub start_server {
  say "This is the server listening on port $port...";
  Mojo::IOLoop->server({
    address => $address,
    port => $port,
    tls => 1,
    tls_cert => 't/cert.pem',
    tls_key  => 't/key.pem',
    # do ask for the client certificate, but don't verify it
    tls_options => {
      SSL_verify_mode => 1,
      SSL_verify_callback => sub { 1 },
    }
  } => sub {
    my ($loop, $stream) = @_;
    my $data = { buffer => '', handler => \&handle_request };
    $stream->on(read => sub {
      my ($stream, $bytes) = @_;
      my $fingerprint = $stream->handle->get_fingerprint();
      $stream->write("Got '$bytes' from client $fingerprint\n");
      $stream->close_gracefully();
    });

t/00_tls_check.t  view on Meta::CPAN


sub query1 {
  my $query = shift;
  # create client
  Mojo::IOLoop->client({
    address => $address,
    port => $port,
    tls => 1,
    tls_cert => 't/cert.pem',
    tls_key  => 't/key.pem',
    # don't verify the server certificate
    tls_options => {SSL_verify_mode => SSL_VERIFY_NONE}
  } => sub {
    my ($loop, $err, $stream) = @_;
    die "Client creation failed: $err\n" if $err;
    $stream->timeout(3);
    $stream->on(error => sub {
      my ($stream, $err) = @_;
      die "Stream error: $err\n" if $err });
    $stream->on(read => sub {
      my ($stream, $bytes) = @_;
      my $fingerprint = 'sha256$0ba6ba61da1385890f611439590f2f0758760708d1375859b2184dcd8f855a00';

t/00_tls_check.t  view on Meta::CPAN

    $stream->write("$query")
  });
  # Start event loop if necessary
  Mojo::IOLoop->start unless Mojo::IOLoop->is_running;
}

sub query2 {
  my $query = shift;
  my $socket = IO::Socket::SSL->new(
    PeerHost => $address, PeerPort => $port,
    # don't verify the server certificate
    SSL_verify_mode => SSL_VERIFY_NONE,
    SSL_cert_file => 't/cert.pem',
    SSL_key_file => 't/key.pem', );
  $socket->print("$query");
  undef $/; # slurp
  my $fingerprint = 'sha256$0ba6ba61da1385890f611439590f2f0758760708d1375859b2184dcd8f855a00';
  is(<$socket>, "Got 'Hello2' from client $fingerprint\n", "IO::Socket::SSL");
}

t/Chat.t  view on Meta::CPAN


my @tests = ("^# Welcome to localhost" => "test",
	     "^localhost: test" => "hallo",
             "^localhost: hallo");
Mojo::IOLoop->client({
  address => $host,
  port => $port,
  tls => 1,
  tls_cert => "t/cert.pem",
  tls_key => "t/key.pem",
  tls_options => { SSL_verify_mode => 0x00 }} => sub {
    my ($loop, $err, $stream) = @_;
    $stream->on(read => sub {
      my ($stream, $bytes) = @_;
      my $text = encode_utf8 $bytes;
      warn $text if $ENV{TEST_VERBOSE};
      # test something
      my $re = shift(@tests);
      like($text, qr/$re/m, $re) if $re;
      my $response = shift(@tests);
      query_gemini("$say_url?$response") if $response;

t/Gopher.t  view on Meta::CPAN

require './t/test.pl';

sub query_gopher {
  my $query = shift;
  my $tls = shift;
  # create client
  my $socket;
  if ($tls) {
    $socket = IO::Socket::SSL->new(
      PeerHost => $host, PeerPort => $gophers_port,
      SSL_verify_mode => SSL_VERIFY_NONE)
	or die "Cannot construct client socket: $@";
  } else {
    $socket = IO::Socket::IP->new("$host:$gopher_port")
	or die "Cannot construct client socket: $@";
  }
  $socket->print("$query\r\n");
  undef $/; # slurp
  return <$socket>;
}

t/Gopher.t  view on Meta::CPAN

like(query_gopher("do/index", 1), qr/^02021-02-05\tpage\/2021-02-05\tlocalhost\t$gophers_port$/m, "Index via TLS");

# match
like(query_gopher("do/match\t05"), qr/^02021-02-05\tpage\/2021-02-05\tlocalhost\t$gopher_port$/m, "Match");
like(query_gopher("do/match\t05", 1), qr/^02021-02-05\tpage\/2021-02-05\tlocalhost\t$gophers_port$/m, "Match via TLS");

# search
like(query_gopher("do/search\tyo"), qr/^02021-02-05\tpage\/2021-02-05\tlocalhost\t$gopher_port$/m, "Search");
like(query_gopher("do/search\tyo", 1), qr/^02021-02-05\tpage\/2021-02-05\tlocalhost\t$gophers_port$/m, "Search via TLS");

# verify that gemini still works
$page = query_gemini("$base/");
like($page, qr/Welcome to Phoebe/, "Main menu via Gemini");
like($page, qr/^Blog:/m, "Main menu (Blog section) via Gemini");
like($page, qr/^=> $base\/page\/2021-02-05 2021-02-05/m, "Main menu contains 2021-02-05 via Gemini");

done_testing();

t/Iapetus.t  view on Meta::CPAN

# test page
my $page = query_gemini("$base/Haiku");
like($page, qr/^51 Path not found/m, "Test page does not exist");

# upload text
sub iapetus {
  my $request = shift;
  my $data = shift;
  my $socket = IO::Socket::SSL->new(
    PeerHost => $host, PeerPort => $port,
    # don't verify the server certificate
    SSL_verify_mode => SSL_VERIFY_NONE,
    SSL_cert_file => 't/cert.pem',
    SSL_key_file => 't/key.pem', );
  $socket->print($request);
  is(<$socket>, "10 Continue\r\n");
  $socket->print($data);
  undef $/; # slurp
  return <$socket>;
}

my $haiku = <<EOT;

t/Spartan.t  view on Meta::CPAN

  return <$socket>;
}

mkdir("$dir/localhost");
mkdir("$dir/localhost/page");
write_text("$dir/localhost/page/2021-02-05.gmi", "yo");
mkdir("$dir/localhost/alex");
mkdir("$dir/localhost/alex/page");
write_text("$dir/localhost/alex/page/2021-02-05.gmi", "lo");

# verify we get single digit errors
like(query_spartan(""), qr/^4 /, "No empty path");

my $page = query_spartan("/");
like($page, qr/^# Welcome to Phoebe/m, "Main menu");
like($page, qr/^Blog:/m, "Main menu (Blog section)");

like($page, qr(^=> spartan://localhost:$spartan_port/page/2021-02-05 2021-02-05$)m, "Main menu (Blog link)");
like($page, qr(^=> spartan://localhost:$spartan_port/do/index Index of all pages$)m, "Page index link");

$page = query_spartan("/page/2021-02-05");

t/Spartan.t  view on Meta::CPAN

like($page, qr(^lo$)m, "Different Page Text in a Space (gemini)");
$page = query_spartan("/alex/raw/2021-02-05");
like($page, qr(^lo$)m, "Different Page Text in a Space (raw)");
$page = query_spartan("/alex/html/2021-02-05");
like($page, qr(^<p>lo$)m, "Different Page Text in a Space (html)");

# page list
like(query_spartan("/do/index"),
     qr(^=> spartan://localhost:$spartan_port/page/2021-02-05 2021-02-05$)m, "Index");

# verify that gemini still works
$page = query_gemini("$base/");
like($page, qr/Welcome to Phoebe/, "Main menu via Gemini");
like($page, qr/^Blog:/m, "Main menu (Blog section) via Gemini");
like($page, qr/^=> $base\/page\/2021-02-05 2021-02-05/m, "Main menu contains 2021-02-05 via Gemini");

done_testing();

t/spaces.t  view on Meta::CPAN


# test the main space

my $page = query_gemini("$base/");
like($page, qr/^=> $base\/page\/Alex Alex/m, "main menu contains Alex");
$page = query_gemini("$base/page/Alex");
like($page, qr/^Alex Schroeder/m, "Alex page was created");
$page = query_gemini("$base/file/alex.jpg");
like($page, qr/^20 image\/jpeg\r\n/, "alex.jpg file was created");

# verify that the alex space has different page content

$page = query_gemini("$base/alex");
like($page, qr/^=> $base\/alex\/page\/Alex Alex/m, "main menu contains Alex");
$page = query_gemini("$base/alex/page/Alex");
like($page, qr/^This page does not yet exist/m, "Alex page is empty in the alex space");

# redirect of reserved word
$page = query_gemini("$base/alex/do");
is($page, "31 $base/alex\r\n", "Redirect reserved word");

t/test.pl  view on Meta::CPAN

  my ($header, $mimetype, $encoding, $buffer);

  # create client
  Mojo::IOLoop->client(
    {
      address => "localhost",
      port => $port,
      tls => 1,
      tls_cert => ($cert ? "t/cert.pem" : undef),
      tls_key => ($cert ? "t/key.pem" : undef),
      tls_options => { SSL_verify_mode => 0x00 },
    } => sub {
      my ($loop, $err, $stream) = @_;
      die "Client creation failed: $err\n" if $err;
      $stream->timeout(2);
      $stream->on(error => sub {
	my ($stream, $err) = @_;
	die "Stream error: $err\n" if $err });
      $stream->on(close => sub {
	my ($stream) = @_;
	diag "Closing stream\n" if $ENV{TEST_VERBOSE} });



( run in 1.112 second using v1.01-cache-2.11-cpan-13bb782fe5a )