1 # Simple Photo Gallery: Web Interface
2 # (c) 2003--2021 Martin Mares <mj@ucw.cz>
4 package UCW::Gallery::Web;
18 'i' => { 'var' => \$show_img, 'check' => '\d+' },
19 'a' => { 'var' => \$want_archive },
20 'pw' => { 'var' => \$auth_password },
24 print "<p style='color:red'>Bad luck, the script is broken. Sorry.\n<p>$_[0]\n";
25 print "</body></html>\n";
29 my ($self, $key) = @_;
30 return $self->{gal}->get($key);
34 my ($self, $key) = @_;
35 return $self->{gal}->try_get($key);
39 my ($self, $key) = @_;
40 my $val = $self->get($key);
41 if (ref $val eq 'CODE') {
48 # For use by extras hooks
54 # For use by extras hooks: return true if we are showing an image page, false for index page
55 sub showing_image($) {
57 return $show_img ne "";
62 my $title = $self->get('Title');
63 my $hextras = $self->extras('WebHeadExtras');
64 my $theme_hextras = $self->theme_head_extras;
66 Content-Type: text/html
68 <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
70 $hextras$theme_hextras<title>$title</title>
74 $UCW::CGI::ErrorHandler::error_hook = \&error;
76 # WebTopExtras are evaluated separately, since they can override the error hook
77 print $self->extras('WebTopExtras');
82 print $self->extras('WebBotExtras'), "</body></html>\n";
88 if ($show_img < 1 || $show_img > $self->{num_photos}) {
89 UCW::CGI::http_error('404 No such photo');
93 my $meta = $self->{meta};
94 my $id = $meta->{sequence}->[$show_img-1];
95 my $m = $meta->{photo}->{$id} or die;
98 $self->show_links(($show_img > 1 ? ("?i=".($show_img-1)) : ""),
100 ($show_img < $self->{num_photos} ? ("?i=".($show_img+1)) : ""));
102 my $t = UCW::CGI::html_escape($m->{title});
105 print "<h1>$t</h1>\n" if $t ne "";
106 my $img = $self->get('PhotoUrlPrefix') . $self->{gal}->photo_file_name($m, $id);
107 print "<p class=large><img src='$img' width=$w height=$h alt='$t'>\n";
112 sub show_pre_thumbs($) {
116 sub show_post_thumbs($) {
120 sub show_common_header($) {
124 $self->show_links($self->get('BackURL'), $self->get('ParentURL'), $self->get('FwdURL'));
125 print "<h1>", $self->get('Title'), "</h1>\n";
126 my $subtitle = $self->get('SubTitle');
127 print "<h2>$subtitle</h2>\n" if $subtitle ne "";
132 $self->show_common_header;
133 $self->show_pre_thumbs;
135 my $meta = $self->{meta};
136 for my $idx (1..$self->{num_photos}) {
137 my $id = $meta->{sequence}->[$idx-1];
138 my $m = $meta->{photo}->{$id};
140 if ($self->get('WebImageSubpages')) {
141 $click_url = "?i=$idx";
143 $click_url = $self->get('PhotoUrlPrefix') . $self->{gal}->photo_file_name($m, $id);
145 $self->show_thumb($meta, $id, $click_url);
148 $self->show_post_thumbs;
152 sub show_login_page($$) {
153 my ($self, $login_failed) = @_;
155 $self->show_common_header;
157 print "<div class='gal-login'>\n";
158 print $self->extras('WebLoginExtras');
160 my $wrong = $login_failed ? " class='gal-login-bad'" : "";
161 print "\t<form method=POST action='.'>\n";
162 print "\t\t<input type=password name=pw$wrong>\n";
163 print "\t\t<input type=submit value='Login'>\n";
173 my $needed = $self->auth_get_needed;
174 @$needed or return 1;
176 if (length $auth_password) {
177 my $passwords = $self->get('AuthPasswords');
179 for my $zone (@$needed) {
180 if (defined $passwords->{$zone} && $passwords->{$zone} eq $auth_password) {
182 my $path = $self->try_get('AuthCookiePath');
183 push @opts, 'path', $path if defined $path;
184 push @opts, 'secure', undef if $self->get('AuthCookieSecure');
185 UCW::CGI::set_cookie($self->get('AuthCookiePrefix') . $zone, $self->auth_zone_hash($zone), @opts);
191 my $abs = $self->try_get('WebAbsURL');
193 print "Status: 303\n";
194 print "Location: $abs\n\n";
200 $self->show_login_page($match == 0);
204 my $known_tokens = $self->auth_parse_cookies;
205 for my $zone (@$needed) {
206 return 1 if $known_tokens->{$zone};
209 $self->show_login_page(0);
213 sub auth_get_needed($) {
216 my $auth = $self->get('AuthNeeded');
217 defined $auth or return [];
221 $auth ne "" or return [];
226 sub auth_parse_cookies($) {
228 my $known_tokens = {};
230 my %cookies = UCW::CGI::parse_cookies;
231 my $px = $self->get('AuthCookiePrefix');
232 for my $k (keys %cookies) {
233 if (substr($k, 0, length $px) eq $px) {
234 my $zone = substr($k, length $px);
235 my $v = $cookies{$k};
236 if ($v eq $self->auth_zone_hash($zone)) {
237 $known_tokens->{$zone} = 1;
239 print STDERR "Gallery: Invalid auth cookie for zone $zone\n";
244 return $known_tokens;
247 sub auth_zone_hash($) {
248 my ($self, $zone) = @_;
249 my $secret = $self->get('AuthSecret');
250 return substr(Digest::SHA::hmac_sha256_hex($zone, $secret), 0, 16);
255 binmode STDOUT, ':utf8';
256 UCW::CGI::parse_args(\%args);
258 $self->auth_check or return;
260 $self->{meta} = $self->{gal}->read_meta(File::Spec->catfile($self->get('CacheDir'), 'cache.meta'));
261 $self->{num_photos} = scalar @{$self->{meta}->{sequence}};
264 require UCW::Gallery::Archive;
265 UCW::Gallery::Archive::send_archive($self->{gal}, $self->{meta});
266 } elsif ($show_img ne "") {
274 my ($class, $gal) = @_;
275 my $self = { gal => $gal };
279 # Extras are either strings or functions called with the current gallery object as parameter
283 WebLoginExtras => "",
285 # Used by the theming logic
286 WebThemeCSS => undef,
288 # 1 if thumbnail link to sub-pages with images, 0 if they link directly to image files
289 WebImageSubpages => 1,
291 # If enabled, calling the CGI with a=zip produces a ZIP archive with all photos.
292 WebAllowArchives => 1,
294 # Optional absolute URL of the current gallery. This is useful for redirects after login.
295 # If it is not set, form POST does not redirect to GET.
298 # Authentication: Known zones and their passwords.
299 # AuthPasswords => { 'zone' => 'passwd' },
301 # Authentication: Which zones have access to the current gallery.
304 # Authentication: Secret used for encryption of cookies.
307 # Authentication: Properties of cookies.
308 AuthCookiePrefix => 'gal_',
309 AuthCookiePath => undef,
310 AuthCookieSecure => 1,