App-Phoebe

 view release on metacpan or  search on metacpan

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

# -*- mode: perl -*-
# Copyright (C) 2017–2021  Alex Schroeder <alex@gnu.org>

# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, either version 3 of the License, or (at your option) any
# later version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <https://www.gnu.org/licenses/>.

=encoding utf8

=head1 NAME

App::Phoebe::Capsules - provide every visitor with a writeable capsule

=head1 DESCRIPTION

By default, Phoebe creates a wiki editable by all. With this extension, the
C</capsule> space turns into a special site: if you have a client certificate,
you automatically get an editable capsule with an assigned fantasy name.

Simply add it to your F<config> file. If you are virtual hosting, name the host
or hosts for your capsules.

    package App::Phoebe::Capsules;
    use Modern::Perl;
    our @capsule_hosts = qw(transjovian.org);
    use App::Phoebe::Capsules;

Every client certificate gets assigned a capsule name.

You can provide a link with some documentation, if you want:

    our $capsule_help = '//transjovian.org/phoebe/page/Capsules';

=head1 NO MIME TYPES

When uploading to a capsule, the MIME type is ignored. Instead, it is determined
from the filename extension.

=head1 TROUBLESHOOTING

🔥 In the wiki directory, you can have a file called F<fingerprint_equivalents>.
Its main use is to allow people to add more fingerprints for their site, such as
from other devices or friends. The file format is line oriented, each line
containing two fingerprints, C<FROM> and C<TO>.

🔥 The capsule name I<login> is reserved.

🔥 The file names I<archive>, I<backup>, and I<upload> are reserved.

=head1 NO WIKI, ONLY CAPSULES

Here's how to disable all wiki functions of Phoebe and just use capsules. The
C<nothing_else> function comes right after C<capsules> as an extension and
always returns 1, so Phoebe considers this request handled. Therefore, the
regular request handlers won't get used. Make sure that any extensions you do
want to have are prepended to C<@extensions> after setting it (using
C<unshift>).

    # tested by t/example-capsules-only.t
    package App::Phoebe::Capsules;
    use Modern::Perl;
    use App::Phoebe qw($log @request_handlers @extensions);
    use App::Phoebe::Capsules;
    our $capsule_help = '//transjovian.org/phoebe/page/Capsules';
    our $capsule_space;
    @extensions = (\&capsules, \&nothing_else);
    sub nothing_else {
      my ($stream, $url) = @_;
      $log->info("No handler for $url: only capsules!");
      result($stream, "30", "/$capsule_space");
      1;
    }
    $log->info('Only capsules!');
    1;

=cut

package App::Phoebe::Capsules;
use App::Phoebe qw($server $log @extensions @request_handlers host_regex port success result print_link wiki_dir
		   valid_id valid_mime_type valid_size to_url);
use File::Slurper qw(read_dir read_binary write_binary);
use Net::IDN::Encode qw(domain_to_ascii);
use Encode qw(encode_utf8 decode_utf8);
use File::MimeInfo qw(globs);
use List::Util qw(sum first);
use Modern::Perl;
use URI::Escape;

push(@extensions, \&capsules);

our $capsule_space = "capsule";
our @capsule_hosts;
our $capsule_help;
our @capsule_tokens;
our %capsule_equivalent;

# load fingerprint equivalents on the next tick
Mojo::IOLoop->next_tick(sub {
  my $dir = $server->{wiki_dir};
  if (-f "$dir/fingerprint_equivalents") {
    my $bytes = read_binary("$dir/fingerprint_equivalents");
    %capsule_equivalent = split(' ', $bytes);
  } } );

sub capsules {
  my $stream = shift;
  my $url = shift;
  my $hosts = capsule_regex();
  my $port = port($stream);
  my ($host, $capsule, $id, $token);
  if ($url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/upload$!) {
    return result($stream, "10", "Filename");
  } elsif (($host, $capsule, $id) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/upload\?([^/]+)$!) {
    $capsule = decode_utf8(uri_unescape($capsule));
    return result($stream, "30", "gemini://$host:$port/$capsule_space/$capsule/$id");
  } elsif (($host) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/login$!) {
    return serve_capsule_login($stream, $host);
  } elsif (($host, $capsule) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/archive$!) {
    return serve_capsule_archive($stream, $host, decode_utf8(uri_unescape($capsule)));
  } elsif (($host, $capsule, $id) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/backup(?:/([^/]+))?$!) {
    return serve_capsule_backup($stream, $host, map { decode_utf8(uri_unescape($_)) } $capsule, $id||"");
  } elsif (($host, $capsule, $id) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/delete(?:/([^/]+))?$!) {
    return serve_capsule_delete($stream, $host, map { decode_utf8(uri_unescape($_)) } $capsule, $id||"");
  } elsif ($url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/access$!) {
    return result($stream, "10", "Password");
  } elsif (($host, $capsule, $token) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/access\?(.+)$!) {
    return serve_capsule_access($stream, $host, decode_utf8(uri_unescape($capsule)), decode_utf8(uri_unescape($token)));
  } elsif (($host, $capsule) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/share$!) {
    return serve_capsule_sharing($stream, $host, decode_utf8(uri_unescape($capsule)));
  } elsif (($host, $capsule, $id) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/([^/]+)$!) {
    return serve_capsule_page($stream, $host, map { decode_utf8(uri_unescape($_)) } $capsule, $id);
  } elsif (($host, $capsule) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/([^/]+)/?$!) {
    return serve_capsule_menu($stream, $host, decode_utf8(uri_unescape($capsule)));
  } elsif (($host) = $url =~ m!^gemini://($hosts)(?::$port)?/$capsule_space/?$!) {
    return serve_main_menu($stream, $host);
  }
  return;
}

sub serve_capsule_login {
  my ($stream, $host) = @_;
  my $name = capsule_name($stream);
  if ($name) {
    $log->info("Redirect to capsule");
    result($stream, "30", to_url($stream, $host, $capsule_space, ""));
  } else {



( run in 0.475 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )