| blib/lib/Bryar/Frontend/Base.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 42 | 131 | 32.0 |
| branch | 0 | 46 | 0.0 |
| condition | 0 | 35 | 0.0 |
| subroutine | 11 | 23 | 47.8 |
| pod | 4 | 13 | 30.7 |
| total | 57 | 248 | 22.9 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package Bryar::Frontend::Base; | ||||||
| 2 | 5 | 5 | 101 | use 5.006; | |||
| 5 | 16 | ||||||
| 5 | 178 | ||||||
| 3 | 5 | 5 | 36 | use strict; | |||
| 5 | 10 | ||||||
| 5 | 182 | ||||||
| 4 | 5 | 5 | 24 | use warnings; | |||
| 5 | 9 | ||||||
| 5 | 140 | ||||||
| 5 | 5 | 5 | 23 | use Carp; | |||
| 5 | 18 | ||||||
| 5 | 393 | ||||||
| 6 | our $VERSION = '1.2'; | ||||||
| 7 | 5 | 5 | 1950 | use Time::Piece; | |||
| 5 | 24947 | ||||||
| 5 | 52 | ||||||
| 8 | 5 | 5 | 471 | use Time::Local; | |||
| 5 | 7 | ||||||
| 5 | 307 | ||||||
| 9 | 5 | 5 | 23 | use Digest::MD5 qw(md5_hex); | |||
| 5 | 9 | ||||||
| 5 | 271 | ||||||
| 10 | 5 | 5 | 1662 | use Encode; | |||
| 5 | 20196 | ||||||
| 5 | 492 | ||||||
| 11 | 5 | 5 | 6174 | use HTTP::Date; | |||
| 5 | 9892 | ||||||
| 5 | 10496 | ||||||
| 12 | |||||||
| 13 | =head1 NAME | ||||||
| 14 | |||||||
| 15 | Bryar::Frontend::Base - Base class for frontend classes | ||||||
| 16 | |||||||
| 17 | =head1 SYNOPSIS | ||||||
| 18 | |||||||
| 19 | use base 'Bryar::Frontend::Base'; | ||||||
| 20 | sub obtain_url {...} | ||||||
| 21 | sub obtain_path_info {...} | ||||||
| 22 | sub obtain_args {...} | ||||||
| 23 | sub send_data {...} | ||||||
| 24 | sub send_header {...} | ||||||
| 25 | sub get_header {...} | ||||||
| 26 | |||||||
| 27 | =head1 DESCRIPTION | ||||||
| 28 | |||||||
| 29 | This abstracts the work of front-ending Bryar, to make real front-end | ||||||
| 30 | classes tidier. | ||||||
| 31 | |||||||
| 32 | =head1 METHODS | ||||||
| 33 | |||||||
| 34 | You provide these. | ||||||
| 35 | |||||||
| 36 | =head2 obtain_url | ||||||
| 37 | |||||||
| 38 | Returns the full URL for this page. | ||||||
| 39 | |||||||
| 40 | =head2 obtain_path_info | ||||||
| 41 | |||||||
| 42 | Returns the path info from the server: the part of the URL after | ||||||
| 43 | F |
||||||
| 44 | |||||||
| 45 | =head2 obtain_params | ||||||
| 46 | |||||||
| 47 | Returns a hash of CGI parameters. | ||||||
| 48 | |||||||
| 49 | =head2 send_data | ||||||
| 50 | |||||||
| 51 | Write stuff to the browser. This will only be called once. | ||||||
| 52 | |||||||
| 53 | =head2 send_header | ||||||
| 54 | |||||||
| 55 | Write stuff to the browser, first. | ||||||
| 56 | |||||||
| 57 | =head2 get_header | ||||||
| 58 | |||||||
| 59 | Read a HTTP header. | ||||||
| 60 | |||||||
| 61 | =cut | ||||||
| 62 | |||||||
| 63 | 0 | 0 | 1 | 0 | sub obtain_url { croak "Don't use Bryar::FrontEnd::Base directly"; } | ||
| 64 | 0 | 0 | 1 | 0 | sub obtain_params { croak "Abstract base class. ABSTRACT BASE CLASS."; } | ||
| 65 | |||||||
| 66 | sub parse_args { | ||||||
| 67 | 0 | 0 | 0 | 0 | my $self = shift; | ||
| 68 | 0 | 0 | my $config = shift; | ||||
| 69 | 0 | 0 | my %params = $self->obtain_params(); | ||||
| 70 | 0 | 0 | my %args = $self->parse_path($config); | ||||
| 71 | 0 | 0 | 0 | if (my $search = $params{search}) { | |||
| 72 | 0 | 0 | 0 | $args{content} = $search if $search =~ /\S{3,}/; # Avoid trivials. | |||
| 73 | } | ||||||
| 74 | 0 | 0 | for (qw(comments format)) { | ||||
| 75 | 0 | 0 | 0 | $args{$_} = $params{$_} if exists $params{$_}; | |||
| 76 | } | ||||||
| 77 | 0 | 0 | 0 | $self->process_new_comment($config, %params) if $params{newcomment}; | |||
| 78 | 0 | 0 | return %args; | ||||
| 79 | } | ||||||
| 80 | |||||||
| 81 | sub parse_path { | ||||||
| 82 | 0 | 0 | 0 | 0 | my ($self, $config) = @_; | ||
| 83 | 0 | 0 | my $pi = $self->obtain_path_info(); | ||||
| 84 | 0 | 0 | my @pi = split m{/}, $pi; | ||||
| 85 | 0 | 0 | 0 | shift @pi while @pi and not$pi[0]; | |||
| 86 | #... | ||||||
| 87 | |||||||
| 88 | 0 | 0 | my %args; | ||||
| 89 | 0 | 0 | 0 | 0 | if ($pi[-1] and $pi[-1] eq "xml") { $args{format} = "xml"; pop @pi; } | ||
| 0 | 0 | ||||||
| 0 | 0 | ||||||
| 90 | 0 | 0 | 0 | 0 | if ($pi[-1] and $pi[-1] =~ /^id_([0-9]+)/) { $args{id} = $1; pop @pi; } | ||
| 0 | 0 | ||||||
| 0 | 0 | ||||||
| 91 | 0 | 0 | 0 | 0 | if ($pi[0] and $pi[0] =~ /^([a-zA-Z]\w*)/ | ||
| 0 | |||||||
| 92 | and $pi[0] !~ /^(?:before)_[0-9]+$/) { # We have a subblog | ||||||
| 93 | 0 | 0 | $args{subblog} = $1; | ||||
| 94 | 0 | 0 | shift @pi; | ||||
| 95 | } | ||||||
| 96 | 0 | 0 | 0 | 0 | if (@pi == 1 and $pi[0] =~ /^before_([0-9]+)$/) { | ||
| 0 | |||||||
| 97 | 0 | 0 | $args{before} = $1; | ||||
| 98 | 0 | 0 | $args{limit} = $config->{recent}; | ||||
| 99 | } elsif (@pi) { # Time/date handling | ||||||
| 100 | 0 | 0 | my ($from, $til) = _make_from_til(@pi); | ||||
| 101 | 0 | 0 | 0 | 0 | if ($from and $til) { | ||
| 102 | 0 | 0 | $args{before} = $til; | ||||
| 103 | 0 | 0 | $args{since} = $from; | ||||
| 104 | } | ||||||
| 105 | } else { | ||||||
| 106 | 0 | 0 | 0 | $args{limit} = $config->{recent} if $args{subblog}; | |||
| 107 | } | ||||||
| 108 | |||||||
| 109 | 0 | 0 | return %args; | ||||
| 110 | } | ||||||
| 111 | |||||||
| 112 | sub process_new_comment { | ||||||
| 113 | 0 | 0 | 0 | 0 | my ($self, $config, %params) = @_; | ||
| 114 | 0 | 0 | my ($doc) = $config->source->search($config, id => $params{id}); | ||||
| 115 | 0 | 0 | 0 | $self->report_error("Couldn't find Doc $params{id}") unless $doc; | |||
| 116 | 0 | 0 | $config->source->add_comment( | ||||
| 117 | $config, | ||||||
| 118 | document => $doc, | ||||||
| 119 | author => $params{author}, | ||||||
| 120 | url => $params{url}, | ||||||
| 121 | email => $params{email}, | ||||||
| 122 | content => $params{content}, | ||||||
| 123 | epoch => time | ||||||
| 124 | ); | ||||||
| 125 | } | ||||||
| 126 | |||||||
| 127 | my $mon = 0; | ||||||
| 128 | my %mons = map { $_ => $mon++ } | ||||||
| 129 | qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); | ||||||
| 130 | |||||||
| 131 | sub _make_from_til { | ||||||
| 132 | 0 | 0 | 0 | my ($y, $m, $d) = @_; | |||
| 133 | 0 | 0 | 0 | if (!$y) { return (0,0) } | |||
| 0 | 0 | ||||||
| 134 | 0 | 0 | my ($fm, $tm) = (0, 11); | ||||
| 135 | 0 | 0 | 0 | 0 | if ($m and exists $mons{$m}) { $fm = $tm = $mons{$m}; } | ||
| 0 | 0 | ||||||
| 136 | 0 | 0 | my ($fd, $td); | ||||
| 137 | 0 | 0 | 0 | if ($d) { $fd = $td = $d } | |||
| 0 | 0 | ||||||
| 138 | else { | ||||||
| 139 | 0 | 0 | $fd = 1; | ||||
| 140 | 0 | 0 | my $when = timelocal(0,0,0,1, $tm, $y); | ||||
| 141 | 0 | 0 | $td = Time::Piece->new($when)->month_last_day; | ||||
| 142 | } | ||||||
| 143 | 0 | 0 | return (timelocal(0,0,0, $fd, $fm, $y), | ||||
| 144 | timelocal(59,59,23, $td, $tm, $y)); | ||||||
| 145 | } | ||||||
| 146 | |||||||
| 147 | |||||||
| 148 | =head2 output | ||||||
| 149 | |||||||
| 150 | $self->output | ||||||
| 151 | |||||||
| 152 | Output the entire blog data to the browser | ||||||
| 153 | |||||||
| 154 | =cut | ||||||
| 155 | |||||||
| 156 | sub output { | ||||||
| 157 | 0 | 0 | 1 | 0 | my ($self, $ct, $data, $last_modified, $headers) = @_; | ||
| 158 | 0 | 0 | 0 | $headers ||= { }; | |||
| 159 | |||||||
| 160 | 0 | 0 | 0 | $self->send_header('Content-Type', $ct) if not $headers->{'Content-Type'}; | |||
| 161 | |||||||
| 162 | 0 | 0 | 0 | 0 | if (not $headers->{Status} and $self->not_modified($last_modified, $data)) { | ||
| 163 | 0 | 0 | $self->send_header($_, $headers->{$_}) foreach keys %$headers; | ||||
| 164 | 0 | 0 | $self->send_header('Status', '304 Not Modified'); | ||||
| 165 | 0 | 0 | $self->send_header('Content-Length', 0); | ||||
| 166 | 0 | 0 | $self->send_data(''); | ||||
| 167 | } else { | ||||||
| 168 | 0 | 0 | $self->send_header($_, $headers->{$_}) foreach keys %$headers; | ||||
| 169 | 0 | 0 | $self->send_header('Content-Length', bytes::length($data)); | ||||
| 170 | 0 | 0 | $self->send_data($data); | ||||
| 171 | } | ||||||
| 172 | } | ||||||
| 173 | |||||||
| 174 | sub not_modified { | ||||||
| 175 | 0 | 0 | 0 | 0 | my ($self, $last_modified, $data) = @_; | ||
| 176 | |||||||
| 177 | # Each method outputs a header as a side effect, so they cannot be | ||||||
| 178 | # combined in a single test. | ||||||
| 179 | 0 | 0 | my $t1 = $self->check_last_modified($last_modified); | ||||
| 180 | 0 | 0 | my $t2 = $self->check_etag($data); | ||||
| 181 | 0 | 0 | 0 | return $t1 and $t2; | |||
| 182 | } | ||||||
| 183 | |||||||
| 184 | sub check_etag { | ||||||
| 185 | 0 | 0 | 0 | 0 | my ($self, $data) = @_; | ||
| 186 | 0 | 0 | 0 | my $req_tag = $self->get_header("If-None-Match") || ''; | |||
| 187 | 0 | 0 | my $etag = '"'.md5_hex(Encode::encode_utf8($data)).'"'; | ||||
| 188 | 0 | 0 | $self->send_header('ETag', $etag); | ||||
| 189 | 0 | 0 | return $etag eq $req_tag; | ||||
| 190 | } | ||||||
| 191 | |||||||
| 192 | sub check_last_modified { | ||||||
| 193 | 0 | 0 | 0 | 0 | my ($self, $last_modified) = @_; | ||
| 194 | 0 | 0 | 0 | return 0 if not $last_modified; | |||
| 195 | |||||||
| 196 | 0 | 0 | my $last_modified_str = HTTP::Date::time2str($last_modified); | ||||
| 197 | 0 | 0 | $self->send_header('Last-Modified', $last_modified_str); | ||||
| 198 | |||||||
| 199 | 0 | 0 | 0 | my $since = $self->get_header('If-Modified-Since') || return 0; | |||
| 200 | 0 | 0 | $since =~ s/;.+$//; # remove any parameters | ||||
| 201 | |||||||
| 202 | 0 | 0 | 0 | return 1 if $since eq $last_modified_str; # optimization | |||
| 203 | 0 | 0 | 0 | my $since_epoch = HTTP::Date::str2time($since) || return 0; | |||
| 204 | 0 | 0 | 0 | return 1 if $since_epoch >= $last_modified; | |||
| 205 | |||||||
| 206 | 0 | 0 | return 0; # modified | ||||
| 207 | } | ||||||
| 208 | |||||||
| 209 | =head2 report_error | ||||||
| 210 | |||||||
| 211 | $self->report_error($title, $message) | ||||||
| 212 | |||||||
| 213 | Used when something went horribly wrong inside Bryar. Spits out the | ||||||
| 214 | error in as friendly a way as possible to the browser, HTML-escaped | ||||||
| 215 | and enclosed by a tag, and to STDERR. |
||||||
| 216 | |||||||
| 217 | =cut | ||||||
| 218 | |||||||
| 219 | sub report_error_browser { | ||||||
| 220 | 1 | 1 | 0 | 7 | my ($class, $title, $message) = @_; | ||
| 221 | 1 | 20 | $class->send_header("Content-type", "text/html"); | ||||
| 222 | 1 | 6 | $class->send_header('Status', '500'); | ||||
| 223 | 1 | 26 | $class->send_data( | ||||
| 224 | "\n" . | ||||||
| 225 | " |
||||||
| 226 | "$title$message" |
||||||
| 227 | ); | ||||||
| 228 | } | ||||||
| 229 | |||||||
| 230 | sub report_error_html { | ||||||
| 231 | 0 | 0 | 0 | 0 | my ($class, $title, $message) = @_; | ||
| 232 | 0 | 0 | $class->report_error_browser($title, $message); | ||||
| 233 | 0 | 0 | croak "$title: $message"; | ||||
| 234 | } | ||||||
| 235 | |||||||
| 236 | sub report_error { | ||||||
| 237 | 1 | 1 | 1 | 10 | my ($class, $title, $message) = @_; | ||
| 238 | 1 | 6 | my ($texttitle, $textmessage) = ($title, $message); | ||||
| 239 | 1 | 101 | $title =~ s/&/&/g; $title =~ s/</g; $title =~ s/>/>/g; | ||||
| 1 | 11 | ||||||
| 1 | 8 | ||||||
| 240 | 1 | 3 | $message =~ s/&/&/g; $message =~ s/</g; $message =~ s/>/>/g; | ||||
| 1 | 7 | ||||||
| 1 | 6 | ||||||
| 241 | 1 | 7 | $message = " $message "; |
||||
| 242 | 1 | 82 | $class->report_error_browser($title, $message); | ||||
| 243 | 1 | 1113 | croak "$texttitle: $textmessage"; | ||||
| 244 | } | ||||||
| 245 | |||||||
| 246 | sub init { | ||||||
| 247 | 0 | 0 | 0 | my ($self, $config) = @_; | |||
| 248 | 0 | my $url = $self->obtain_url(); | |||||
| 249 | 0 | 0 | if (!$config->baseurl) { | ||||
| 250 | 0 | 0 | $config->baseurl($url) if $url =~ s/((bryar|blosxom).cgi).*/$1/; | ||||
| 251 | } | ||||||
| 252 | } | ||||||
| 253 | =head1 LICENSE | ||||||
| 254 | |||||||
| 255 | This module is free software, and may be distributed under the same | ||||||
| 256 | terms as Perl itself. | ||||||
| 257 | |||||||
| 258 | =head1 AUTHOR | ||||||
| 259 | |||||||
| 260 | Copyright (C) 2003, Simon Cozens C |
||||||
| 261 | |||||||
| 262 | some parts Copyright 2007 David Cantrell C |
||||||
| 263 | |||||||
| 264 | |||||||
| 265 | =head1 SEE ALSO | ||||||
| 266 | |||||||
| 267 | =cut | ||||||
| 268 | |||||||
| 269 | 1; |