App-phoebe

 view release on metacpan or  search on metacpan

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

  my $host = shift;
  my $space = shift;
  my $term = shift;
  $log->info("Serving pages matching $term");
  success($stream);
  $stream->write(encode_utf8 "# Pages matching ‘$term’\n");
  my $url = "$oddmuse_wikis{$host}?raw=1;action=index;match=" . uri_escape_utf8($term);
  my @pages = split(/\n/, oddmuse_get_raw($stream, $url)) // return;
  for my $id (@pages) {
    print_link($stream, $host, $space, normal_to_free($id), "page/$id");
  }
}

sub oddmuse_serve_search {
  my $stream = shift;
  my $host = shift;
  my $space = shift;
  my $term = shift;
  $log->info("Serving search for $term");
  success($stream);
  $stream->write(encode_utf8 "# Search ‘$term’\n");
  my $url = "$oddmuse_wikis{$host}?raw=1&search=" . uri_escape_utf8($term);
  my $page = oddmuse_get_raw($stream, $url) // return;
  my @entries = split(/\n\n+/, $page);
  shift @entries; # skip head
  foreach my $entry (@entries) {
    my $data = parse_data($entry);
    my $id = $data->{title};
    print_link($stream, $host, $space, normal_to_free($id), "page/$id");
  }
}

sub oddmuse_serve_rss {
  my $stream = shift;
  my $host = shift;
  my $space = shift;
  my $action = shift;
  my $scheme = 'gemini';
  my $port = port($stream);
  $log->info("Serving Gemini RSS");
  success($stream, "application/rss+xml");
  $stream->write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
  $stream->write("<rss version=\"2.0\" xmlns:atom=\"http://www.w3.org/2005/Atom\">\n");
  my $url = "$oddmuse_wikis{$host}?action=$action;raw=1;full=1";
  if ($space) {
    $url .= ";ns=$space";
  }
  my $page = oddmuse_get_raw($stream, $url) // return;
  my @entries = split(/\n\n+/, $page);
  my $entry = shift @entries;
  my $data = parse_data($entry);
  $stream->write("<channel>\n");
  $stream->write(encode_utf8 "<title>" . quote_html($data->{title}) . "</title>\n");
  $stream->write(encode_utf8 "<description>" . quote_html($data->{description}) . "</description>\n");
  $stream->write("<link>$scheme://$host:$port/</link>\n");
  $stream->write("<atom:link rel=\"self\" type=\"application/rss+xml\" href=\"$scheme://$host:$port/do/rss\" />\n");
  $stream->write("<generator>Phoebe + Config</generator>\n");
  $stream->write("<docs>http://blogs.law.harvard.edu/tech/rss</docs>\n");
  my $dir = $oddmuse_wiki_dirs{$host};
  my ($sec, $min, $hour, $mday, $mon, $year) = gmtime(modified("$dir/pageidx"));
  $stream->write("<updated>"
      . sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ", $year + 1900, $mon + 1, $mday, $hour, $min, $sec)
      . "</updated>\n");
  while (@entries) {
    $data = parse_data(shift(@entries));
    $stream->write("<item>\n");
    # namespaces
    my $ns;
    my $title = $data->{title};
    if ($title =~ /:/) {
      ($ns, $title) = split(/:/, $title);
    }
    $stream->write(encode_utf8 "<title>" . quote_html($data->{title}) . "</title>\n");
    my $link = "gemini://$host:$port/" . ($ns ? "$ns/" : "") . "page/" . uri_escape_utf8(free_to_normal($title));
    $stream->write("<link>$link</link>\n");
    $stream->write("<guid>$link</guid>\n");
    $link = "gemini://$host:$port/" . ($ns ? "$ns/" : "") . "page/Comments_on_" . uri_escape_utf8(free_to_normal($title));
    $stream->write("<comments>$link</comments>\n");
    my $summary = quote_html(oddmuse_gemini_text($stream, $host, $space, $data->{description}));
    $stream->write(encode_utf8 "<description>$summary</description>\n") if $summary;
    # timestamp from 2020-07-22T20:59Z back to a number
    my $ts = $data->{"last-modified"};
    $ts =~ s/Z/:00Z/; # apparently seconds are mandatory?
    $ts = DateTime::Format::ISO8601->parse_datetime($ts)->epoch();
    my ($sec, $min, $hour, $mday, $mon, $year, $wday) = gmtime($ts); # Sat, 07 Sep 2002 00:00:01 GMT
    $stream->write("<pubDate>"
	. sprintf("%s, %02d %s %04d %02d:%02d:%02d GMT", qw(Sun Mon Tue Wed Thu Fri Sat)[$wday], $mday,
		  qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec)[$mon], $year + 1900, $hour, $min, $sec)
	. "</pubDate>\n");
    $stream->write("</item>\n");
  };
  $stream->write("</channel>\n");
  $stream->write("</rss>\n");
}

sub oddmuse_serve_atom {
  my $stream = shift;
  my $host = shift;
  my $space = shift;
  my $action = shift;
  my $port = port($stream);
  $log->info("Serving Gemini Atom");
  success($stream, "application/atom+xml");
  $stream->write(qq{<?xml version="1.0" encoding="UTF-8"?>\n});
  $stream->write("<feed xmlns=\"http://www.w3.org/2005/Atom\">\n");
  $stream->write("<link href=\"gemini:/$host:$port/\"/>\n");
  $stream->write("<link rel=\"self\" type=\"application/atom+xml\" href=\"gemini://$host:$port/do/atom\"/>\n");
  $stream->write("<id>gemini:/$host:$port/do/atom</id>\n");
  my ($sec, $min, $hour, $mday, $mon, $year) = gmtime(modified("$oddmuse_wiki_dirs{$host}/pageidx"));
  $stream->write("<updated>"
      . sprintf("%04d-%02d-%02dT%02d:%02d:%02dZ", $year + 1900, $mon + 1, $mday, $hour, $min, $sec)
      . "</updated>\n");
  $stream->write("<generator uri=\"gemini://$host:$port/\" version=\"1.0\">Gemini Wiki + Config</generator>\n");
  # now get the data and print the entries
  my $url = "$oddmuse_wikis{$host}?action=$action;raw=1;full=1";
  if ($space) {
    $url .= ";ns=$space";
  }
  my $page = oddmuse_get_raw($stream, $url) // return;
  my @entries = split(/\n\n+/, $page);
  my $data = parse_data(shift @entries);
  $stream->write(encode_utf8 "<title>" . quote_html($data->{title}) . "</title>\n");
  while (@entries) {
    $data = parse_data(shift @entries);
    $stream->write("<entry>\n");
    my $name = $data->{title};
    $stream->write(encode_utf8 "<title>$name</title>\n");
    my $link = "gemini://$host:$port/page/" . uri_escape_utf8(free_to_normal($name));
    $stream->write("<link href=\"$link\"/>\n");
    $stream->write("<id>$link</id>\n");
    my $summary = quote_html(oddmuse_gemini_text($stream, $host, $space, $data->{description}));
    $stream->write(encode_utf8 "<content type=\"text\">$summary</content>\n") if $summary;
    $stream->write("<updated>$data->{'last-modified'}</updated>\n");
    $stream->write("</entry>\n");
  };
  $stream->write("</feed>\n");
}

sub oddmuse_serve_config {
  my $stream = shift;
  my $file = shift;
  $log->info("Serving Config");
  my $dir = $server->{wiki_dir};
  my @config;
  push(@config, "config") if -f "$dir/config";
  push(@config, map { "conf.d/$_" } grep(/\.p[lm]$/, read_dir("$dir/conf.d"))) if -d "$dir/conf.d";
  $log->debug("Config files found: @config");
  @config = grep(/^$file$/, @config) if $file;
  if (@config == 0) {
    if ($file) {
      result($stream, "40", "This config file does not exist");
    } else {
      result($stream, "40", "This site does not use any config files");
    }
  } elsif (@config == 1) {
    success($stream, 'text/plain');
    $stream->write(encode_utf8 read_text("$dir/" . shift @config));
  } else {
    success($stream);
    $stream->write("# Config Files\n");
    $stream->write("=> /do/config/" . join('/', map { uri_escape($_) } split('/'))
		   . " " . encode_utf8($_) . "\n") for @config;
  }
}

sub oddmuse_comment {
  my $stream = shift;
  my $host = shift;
  my $space = shift;
  my $id = shift;
  my $query = shift; # token or comment
  my $port = port($stream);
  if (not $id) {
    $log->debug("The URL lacks a page name");
    result($stream, "59", "The URL lacks a page name");
    return;
  }
  my $name = oddmuse_fingerprint_name($stream, $host, $query);
  return unless defined $name;
  if (not $query) {
    result($stream, "10", "Short comment");
    return;
  }
  $id = "Comments_on_$id" unless $id =~ /^Comments_on_/;
  my $token = $oddmuse_wiki_tokens{$host};
  $token = $server->{wiki_token}->[0] if not $token and $server->{wiki_token};
  my $ua = Mojo::UserAgent->new;
  my $tx = $ua->post(
    $oddmuse_wikis{$host}
    => {'X-Forwarded-For' => $stream->handle->peerhost}
    => form => {
      title => $id,
      ns => $space,
      aftertext => $query,
      username => $name,
      answer => $token,
      gemini => 1 });
  $log->debug("Got " . $tx->result->code . " response");
  if ($tx->result->code == 302) {
    my $url = "gemini://$host:$port";
    $url .= "/$space" if $space;
    result($stream, "30", "$url/page/" . uri_escape_utf8($id) . "");
    return;
  }
  $stream->write("59 Got HTTP code " . $tx->result->code . " " . $tx->result->message
      . " (" . $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;
  my $file = "$dir/fingerprints";
  %fingerprints = split(/\s+/, read_text($file)) if -e $file;
  # Forget about fingerprints older than 10min.
  for my $fp (keys %fingerprints) {
    delete $fingerprints{$fp} if $fingerprints{$fp} > $now + 600;
  }
  my @tokens;
  push(@tokens, $oddmuse_wiki_tokens{$host}) if $oddmuse_wiki_tokens{$host};
  push(@tokens, @{$server->{wiki_token}}) unless @tokens;
  if (not $fingerprints{$fingerprint}) {
    if (not $token) {
      result($stream, "10", "Token required to edit this wiki");
    } elsif (not grep { $token eq $_ } @tokens) {
      result($stream, "59", "Wrong token");
    } else {
      result($stream, "10", "Short comment");
      $fingerprints{$fingerprint} = $now;
    }
    # Save new or updated fingerprint timestamp.
    write_text($file, join("\n", map { "$_ $fingerprints{$_}" } keys %fingerprints));
    # Return undefined so that the user needs to react to the message above.
    return;
  }
  # Fingerprint found!
  $fingerprints{$fingerprint} = $now;
  # Save new or updated fingerprint timestamp.
  write_text($file, join("\n", map { "$_ $fingerprints{$_}" } keys %fingerprints));
  # User wants to provide no name: use "" so that we have a defined name.
  return ($stream->handle->peer_certificate('cn') || "");
}



( run in 0.755 second using v1.01-cache-2.11-cpan-f56aa216473 )