File Coverage

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 or whatever.
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             "$title\n" .
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;
  1         11  
  1         8  
240 1         3 $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;