App-phoebe
view release on metacpan or search on metacpan
lib/App/Phoebe/Spartan.pm view on Meta::CPAN
# 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::Spartan - implement the Spartan protocol for Phoebe
=head1 DESCRIPTION
This extension serves your Gemini pages via the Spartan protocol and generates a
few automatic pages for you, such as the main page.
B<Warning!> If you install this code, anybody can write to your site using the
Spartan protocol. There is no token being checked.
To configure, you need to specify the Spartan port(s) in your Phoebe config
file. The default port is 300. This is a priviledge port. Thus, you either need
to grant Perl the permission to listen on a priviledged port, or you need to run
Phoebe as a super user. Both are potential security risk, but the first option
is much less of a problem, I think.
If you want to try this, run the following as root:
setcap 'cap_net_bind_service=+ep' $(which perl)
Verify it:
getcap $(which perl)
If you want to undo this:
setcap -r $(which perl)
Once you do that, no further configuration is necessary. Just add the following
to your F<config> file:
use App::Phoebe::Spartan;
The alternative is to use a port number above 1024. Here's a way to do that:
package App::Phoebe::Spartan;
our $spartan_port = 7000; # listen on port 7000
use App::Phoebe::Spartan;
If you don't do any of the above, you'll get a permission error on startup:
"Mojo::Reactor::Poll: Timer failed: Can't create listen socket: Permission
deniedâ¦"
=cut
package App::Phoebe::Spartan;
use App::Phoebe qw($server $log @main_menu get_ip_numbers space host_regex space_regex run_extensions
serve_index serve_page serve_raw serve_html serve_history serve_diff save_page
blog print_link text);
use Modern::Perl;
use URI::Escape;
use Encode qw(encode_utf8 decode_utf8 decode);
use Text::Wrapper;
use utf8;
no warnings 'redefine';
our $spartan_port ||= 300;
use Mojo::IOLoop;
# start the loop after configuration (so that the user can change $spartan_port)
Mojo::IOLoop->next_tick(\&spartan_startup);
sub spartan_startup {
for my $host (keys %{$server->{host}}) {
for my $address (get_ip_numbers($host)) {
for my $port (ref $spartan_port ? @$spartan_port : $spartan_port) {
$log->info("$host: listening on $address:$port (Spartan)");
Mojo::IOLoop->server({
address => $address,
port => $port,
} => sub {
my ($loop, $stream) = @_;
my $buffer;
my $request_host;
my $path;
my $length;
$stream->on(read => sub {
my ($stream, $bytes) = @_;
$log->debug("Received " . length($bytes) . " bytes via Spartan");
$buffer .= $bytes;
if (not $length and $buffer =~ /^(.*)\r\n/) {
my $request_line = $1;
if (($request_host, $path, $length) = $request_line =~ /^(\S+) (\S+) (\d+)/) {
my $re = host_regex();
if ($request_host !~ /^($re)$/) {
result($stream, "4", "We do not serve $request_host!");
$stream->close_gracefully();
return;
}
$buffer =~ s/^.*\r\n//; # strip request line
} else {
result($stream, "4", "This request is garbage!");
$stream->close_gracefully();
return;
}
}
if (defined $length and $length == length($buffer)) {
serve_spartan($stream, $request_host, $path, $length, $buffer);
} else {
$log->debug("Waiting for more bytes...");
}
});
});
}
}
}
}
sub serve_spartan {
( run in 0.585 second using v1.01-cache-2.11-cpan-d7a12ab2c7f )