]> git.sesse.net Git - pr0n/commitdiff
Initial checkin.
authorSteinar H. Gunderson <sesse@debian.org>
Sun, 23 Jul 2006 15:43:57 +0000 (17:43 +0200)
committerSteinar H. Gunderson <sesse@debian.org>
Sun, 23 Jul 2006 15:43:57 +0000 (17:43 +0200)
117 files changed:
faq.html [new file with mode: 0644]
newevent.html [new file with mode: 0644]
perl/Sesse/pr0n/Common.pm [new file with mode: 0644]
perl/Sesse/pr0n/Image.pm [new file with mode: 0644]
perl/Sesse/pr0n/Index.pm [new file with mode: 0644]
perl/Sesse/pr0n/Listing.pm [new file with mode: 0644]
perl/Sesse/pr0n/NewEvent.pm [new file with mode: 0644]
perl/Sesse/pr0n/Overload.pm [new file with mode: 0644]
perl/Sesse/pr0n/Rotate.pm [new file with mode: 0644]
perl/Sesse/pr0n/Select.pm [new file with mode: 0644]
perl/Sesse/pr0n/Single.pm [new file with mode: 0644]
perl/Sesse/pr0n/Templates.pm [new file with mode: 0644]
perl/Sesse/pr0n/WebDAV.pm [new file with mode: 0644]
perl/Sesse/pr0n/pr0n.pm [new file with mode: 0644]
pr0n.css [new file with mode: 0644]
robots.txt [new file with mode: 0644]
skoyen.css [new file with mode: 0644]
templates/bilder.knatten.com/date [new file with mode: 0644]
templates/bilder.knatten.com/event-listing [new file with mode: 0644]
templates/bilder.knatten.com/footer [new file with mode: 0644]
templates/bilder.knatten.com/header [new file with mode: 0644]
templates/bilder.knatten.com/imgsperpage [new file with mode: 0644]
templates/bilder.knatten.com/imgsperpage-unlimited [new file with mode: 0644]
templates/bilder.knatten.com/infobox [new file with mode: 0644]
templates/bilder.knatten.com/infobox-off [new file with mode: 0644]
templates/bilder.knatten.com/infobox-on [new file with mode: 0644]
templates/bilder.knatten.com/nextpage [new file with mode: 0644]
templates/bilder.knatten.com/overloadmode [new file with mode: 0644]
templates/bilder.knatten.com/prevpage [new file with mode: 0644]
templates/bilder.knatten.com/show [new file with mode: 0644]
templates/bilder.knatten.com/show-all [new file with mode: 0644]
templates/bilder.knatten.com/show-selected [new file with mode: 0644]
templates/bilder.knatten.com/submittedby [new file with mode: 0644]
templates/bilder.knatten.com/thispage [new file with mode: 0644]
templates/bilder.knatten.com/thumbsize [new file with mode: 0644]
templates/bilder.knatten.com/viewres [new file with mode: 0644]
templates/bilder.knatten.com/viewres-unlimited [new file with mode: 0644]
templates/default/date [new file with mode: 0644]
templates/default/event-listing [new file with mode: 0644]
templates/default/footer [new file with mode: 0644]
templates/default/header [new file with mode: 0644]
templates/default/imgsperpage [new file with mode: 0644]
templates/default/imgsperpage-unlimited [new file with mode: 0644]
templates/default/infobox [new file with mode: 0644]
templates/default/infobox-off [new file with mode: 0644]
templates/default/infobox-on [new file with mode: 0644]
templates/default/nextpage [new file with mode: 0644]
templates/default/overloadmode [new file with mode: 0644]
templates/default/prevpage [new file with mode: 0644]
templates/default/show [new file with mode: 0644]
templates/default/show-all [new file with mode: 0644]
templates/default/show-selected [new file with mode: 0644]
templates/default/submittedby [new file with mode: 0644]
templates/default/thispage [new file with mode: 0644]
templates/default/thumbsize [new file with mode: 0644]
templates/default/viewres [new file with mode: 0644]
templates/default/viewres-unlimited [new file with mode: 0644]
templates/images.tg05.gathering.org/date [new file with mode: 0644]
templates/images.tg05.gathering.org/event-listing [new file with mode: 0644]
templates/images.tg05.gathering.org/footer [new file with mode: 0644]
templates/images.tg05.gathering.org/header [new file with mode: 0644]
templates/images.tg05.gathering.org/imgsperpage [new file with mode: 0644]
templates/images.tg05.gathering.org/imgsperpage-unlimited [new file with mode: 0644]
templates/images.tg05.gathering.org/infobox [new file with mode: 0644]
templates/images.tg05.gathering.org/infobox-off [new file with mode: 0644]
templates/images.tg05.gathering.org/infobox-on [new file with mode: 0644]
templates/images.tg05.gathering.org/nextpage [new file with mode: 0644]
templates/images.tg05.gathering.org/overloadmode [new file with mode: 0644]
templates/images.tg05.gathering.org/prevpage [new file with mode: 0644]
templates/images.tg05.gathering.org/show [new file with mode: 0644]
templates/images.tg05.gathering.org/show-all [new file with mode: 0644]
templates/images.tg05.gathering.org/show-selected [new file with mode: 0644]
templates/images.tg05.gathering.org/submittedby [new file with mode: 0644]
templates/images.tg05.gathering.org/thispage [new file with mode: 0644]
templates/images.tg05.gathering.org/thumbsize [new file with mode: 0644]
templates/images.tg05.gathering.org/viewres [new file with mode: 0644]
templates/images.tg05.gathering.org/viewres-unlimited [new file with mode: 0644]
templates/itk-bilder.samfundet.no/date [new file with mode: 0644]
templates/itk-bilder.samfundet.no/event-listing [new file with mode: 0644]
templates/itk-bilder.samfundet.no/footer [new file with mode: 0644]
templates/itk-bilder.samfundet.no/header [new file with mode: 0644]
templates/itk-bilder.samfundet.no/imgsperpage [new file with mode: 0644]
templates/itk-bilder.samfundet.no/imgsperpage-unlimited [new file with mode: 0644]
templates/itk-bilder.samfundet.no/infobox [new file with mode: 0644]
templates/itk-bilder.samfundet.no/infobox-off [new file with mode: 0644]
templates/itk-bilder.samfundet.no/infobox-on [new file with mode: 0644]
templates/itk-bilder.samfundet.no/nextpage [new file with mode: 0644]
templates/itk-bilder.samfundet.no/overloadmode [new file with mode: 0644]
templates/itk-bilder.samfundet.no/prevpage [new file with mode: 0644]
templates/itk-bilder.samfundet.no/show [new file with mode: 0644]
templates/itk-bilder.samfundet.no/show-all [new file with mode: 0644]
templates/itk-bilder.samfundet.no/show-selected [new file with mode: 0644]
templates/itk-bilder.samfundet.no/submittedby [new file with mode: 0644]
templates/itk-bilder.samfundet.no/thispage [new file with mode: 0644]
templates/itk-bilder.samfundet.no/thumbsize [new file with mode: 0644]
templates/itk-bilder.samfundet.no/viewres [new file with mode: 0644]
templates/itk-bilder.samfundet.no/viewres-unlimited [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/date [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/event-listing [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/footer [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/header [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/imgsperpage [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/imgsperpage-unlimited [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/infobox [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/infobox-off [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/infobox-on [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/nextpage [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/overloadmode [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/prevpage [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/show [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/show-all [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/show-selected [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/submittedby [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/thispage [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/thumbsize [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/viewres [new file with mode: 0644]
templates/skoyen.bilder.knatten.com/viewres-unlimited [new file with mode: 0644]

diff --git a/faq.html b/faq.html
new file mode 100644 (file)
index 0000000..3ad0efe
--- /dev/null
+++ b/faq.html
@@ -0,0 +1,140 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE
+  html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+  <head>
+    <title>pr0n FAQ</title>
+    <link rel="stylesheet" href="/pr0n.css" type="text/css" />
+  </head>
+  <body>
+    <h1>pr0n FAQ</h1>
+    <p>Last updated July 16th, 2006</p>
+
+    <h2>So, what is this pr0n thing anyway?</h2>
+    
+    <p>pr0n is my very own gallery system. It is used on a few different
+      host names, most notably at <a href="http://pr0n.sesse.net/">pr0n.sesse.net</a>
+      to show images I and others have taken at events I care about (some
+      more than others, of course).</p>
+
+    <h2>Why the name? Is this some kind of fetish site?</h2>
+
+    <p>The name "pr0n" (scriptkiddie-speak for "porn", of course) was just seen
+      as a very good fit for an image gallery. :-) There is no (and will not
+      be any) adult content on this site.</p>
+
+    <h2>Can I upload my own images here?</h2>
+
+    <p>Yes, you can, as long as they're related to one of the events already
+      here. Contact me (see the bottom of the page) for more information.</p>
+
+    <h2>Can I download all the pictures for viewing?</h2>
+   
+    <p>Sorry, no. First of all, please don't use any "web mirroring" program to
+      download all the images -- of course, I can't stop you, but you're
+      putting a lot of unneccessary load on the system. There are two main
+      reasons for not downloading all the pictures: First, there is a
+      question of copyright; not all images here are taken by me, and I've
+      been given permission to display them here, not to pass them on. Second,
+      keep in mind that some of the events contain over 2GB of images --
+      do you really need all that? I'd advise you to crank up the thumbnail
+      size to the maximum possible size instead; it's quite comfortable to
+      browse images on without having to click back and forth all the time.</p>
+
+    <h2>I just changed thumbnail resolution, why is everything so slow?</h2>
+
+    <p>Probably the requested size was never generated before, so the server
+       has to scale all the images. As the scaling method used is geared
+       towards getting good-quality, sharp thumbnails, not speed, this can
+       take a while. It's all getting cached on disk for later re-use, though,
+       so the next time somebody views the same images in that resolution,
+       it will be snappy as usual.</p>
+
+    <h2>Why didn't you just throw up Gallery?</h2>
+
+    <p>Because it didn't fit my needs, and the same goes for all other systems
+      I've seen. I wanted something no-nonsense that would work for <em>my</em>
+      purposes -- I don't want to click around endlessly just to watch some
+      pictures. Others are of course free to do as they wish, I can't impose
+      my will on anybody :-)</p>
+
+    <h2>What are the primary features of pr0n?</h2>
+
+    <p>Mostly that it's no-nonsense and just works, without being in your way.
+      Also, it has dynamical rescaling (of good quality -- proper,
+      sharp thumbnails, no crappy nearest-neighbor scaling) of both thumbnails
+      and images (most client-side scaling sucks quality-wise, unfortunately),
+      an easy-to-use <a href="http://www.webdav.org/">WebDAV</a>-based upload
+      interface and in general good performance (being a set of persistent,
+      optimized Perl modules; I've seen it throw out over 300 hits a second,
+      but I won't guarantee it would withstand a Slashdot attack ;-) ). Also,
+      it has quite OK skinning capabilities, so it's able to adapt into
+      different designs quite easily.</p>
+
+    <h2>What hardware/software does it run on?</h2>
+      
+    <p>pr0n currently runs on an Athlon 64 X2 3800+ with 2GB RAM and ordinary
+      SATA disks. (The server does a lot of other stuff besides running pr0n, of
+      course.) pr0n itself is a custom-made system by myself, tightly coupled
+      into <a href="http://www.apache.org/">Apache</a> 2.0, <a
+      href="http://perl.apache.org/">mod_perl</a> 2.0 and <a
+      href="http://www.imagemagick.org/">ImageMagick</a> 6.x (as well as various
+      other Perl modules), using <a
+      href="http://www.postgresql.org/">PostgreSQL</a> 8.1 as the back-end
+      database for metadata et al. The base operating system is <a
+      href="http://www.debian.org/">Debian</a> etch (ie. “testing”).</p>
+
+    <p>The Perl modules aren't really that big -- we're talking about only
+      approx. 1500 lines of code (of which ~30% is the WebDAV part; I should
+      really make that a bit cleaner once). Most of the real work is done by
+      the software on which pr0n builds on.</p>
+
+    <h2>How much data is there in there, anyway?</h2>
+
+    <p>At the time of writing, approximately 44GB of image data (that is, over
+      35000 different images), plus cache, plus metadata in the SQL database.
+      (These numbers are growing rather rapidly, so they could be outdated at
+      any given time.)</p>
+
+    <h2>Can I get the source?</h2>
+
+    <p>Probably, but are you sure you can get it to work? It's
+      non-trivial to set up, as it depends on pre-release software and a lot of
+      custom configuring; this is not a pre-made, user friendly package for your
+      favourite Linux distribution. You can probably get a tarball, but I'm not
+      going to hold your hand configuring it. :-) If you really want to try,
+      contact me (see the bottom of the page) and we'll see what we can do.</p>
+
+    <h2>Will you implement feature X?</h2>
+
+    <p>Probably not; I have a lot of things to do besides programming new
+      features. Also, I'm not really sure if I want tons of stupid people
+      writing stupid comments under my images, or icky HTML pages with
+      "previous" and "next" buttons instead of just getting directly to
+      the images :-) If you really have a novel or cool feature, feel
+      free to contact me (see below).</p>
+
+    <h2>Is the upload WebDAV server RFC-compliant?</h2>
+
+    <p>Unfortunately, no. When and if somebody makes a sane framework for
+      making WebDAV servers I can use, it probably will, but ATM it's just
+      too much work for what I need it for. It would be a lot easier if
+      I only had to support WebDAV level 1, but due to silly restrictions
+      in Mac OS X' WebDAV client I have to support WebDAV level 2 as well,
+      and, well, most of that is faked. ;-) In addition, there are multiple
+      minor features in the system (like autorenaming files on name clashes)
+      that just aren't easy to adapt to WebDAV. The WebDAV service is
+      restricted, though, so I guess rather few people will get hurt just
+      because I'm not fully compliant ;-)</p>
+
+    <h2>How do I get in touch with you?</h2>
+
+    <p>Try <a href="mailto:sgunderson@bigfoot.com">e-mail</a>, or reach me
+      on IRC as Sesse on EFnet, IRCnet, Freenode or OFTC.</p>
+
+    <hr />
+    <p class="footer">pr0n v2.04,
+      &copy; 2004-2006 <a href="http://www.sesse.net/">Steinar H. Gunderson</a>.</p>
+  </body>
+</html>
diff --git a/newevent.html b/newevent.html
new file mode 100644 (file)
index 0000000..f74e672
--- /dev/null
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE
+  html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+  <head>
+    <title>Ny hendelse</title>
+    <link rel="stylesheet" href="/pr0n.css" type="text/css" />
+  </head>
+  <body>
+    <h1>Ny hendelse</h1>
+
+    <p>Merk at du ikke kan fjerne hendelser når de først er lagt til; hvis du
+      har gjort noe feil må du snakke med Steinar. :-)</p>
+
+    <form method="post" action="/newevent">
+      <p>ID: <input type="text" name="id" /> (f.eks. "revy03", må kun inneholde a-zA-Z0-9 og -, ingen mellomrom)</p>
+      <p>Dato: <input type="text" name="date" /> (f.eks. "27.-31. desember 2003")</p>
+      <p>Beskrivelse: <input type="text" name="desc" /> (f.eks. "Nyttårsrevyen 2003")</p>
+      <p><input type="submit" value="Legg til hendelse" /></p>
+    </form>
+  </body>
+</html>
diff --git a/perl/Sesse/pr0n/Common.pm b/perl/Sesse/pr0n/Common.pm
new file mode 100644 (file)
index 0000000..f915260
--- /dev/null
@@ -0,0 +1,503 @@
+package Sesse::pr0n::Common;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Templates;
+use Sesse::pr0n::Overload;
+
+use Apache2::RequestRec (); # for $r->content_type
+use Apache2::RequestIO ();  # for $r->print
+use Apache2::Const -compile => ':common';
+use Apache2::Log;
+use ModPerl::Util;
+
+use DBI;
+use DBD::Pg;
+use Image::Magick;
+use POSIX;
+use Digest::SHA1;
+use MIME::Base64;
+use MIME::Types;
+use LWP::Simple;
+# use Image::Info;
+use Image::ExifTool;
+
+BEGIN {
+       use Exporter ();
+       our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+       $VERSION     = "v2.04";
+       @ISA         = qw(Exporter);
+       @EXPORT      = qw(&error &dberror);
+       %EXPORT_TAGS = qw();
+       @EXPORT_OK   = qw(&error &dberror);
+
+       our $dbh = DBI->connect("dbi:Pg:dbname=pr0n;host=127.0.0.1", "pr0n", "EsVdwImY")
+               or die "Couldn't connect to PostgreSQL database: " . DBI->errstr;
+       our $mimetypes = new MIME::Types;
+       
+       Apache2::ServerUtil->server->log_error("Initializing pr0n $VERSION");
+}
+END {
+       our $dbh;
+       $dbh->disconnect;
+}
+
+our ($dbh, $mimetypes);
+
+sub error {
+       my ($r,$err,$status,$title) = @_;
+
+       if (!defined($status) || !defined($title)) {
+               $status = 500;
+               $title = "Internal server error";
+       }
+       
+        $r->content_type('text/html; charset=utf-8');
+       $r->status($status);
+
+        header($r, $title);
+       $r->print("    <p>Error: $err</p>\n");
+        footer($r);
+
+       $r->log->error($err);
+
+       ModPerl::Util::exit();
+}
+
+sub dberror {
+       my ($r,$err) = @_;
+       error($r, "$err (DB error: " . $dbh->errstr . ")");
+}
+
+sub header {
+       my ($r,$title) = @_;
+
+       $r->content_type("text/html; charset=utf-8");
+
+       # Fetch quote if we're itk-bilder.samfundet.no
+       my $quote = "";
+       if ($r->get_server_name eq 'itk-bilder.samfundet.no') {
+               $quote = LWP::Simple::get("http://itk.samfundet.no/include/quotes.cli.php");
+               $quote = "Error: Could not fetch quotes." if (!defined($quote));
+       }
+       Sesse::pr0n::Templates::print_template($r, "header", { title => $title, quotes => $quote });
+}
+
+sub footer {
+       my ($r) = @_;
+       Sesse::pr0n::Templates::print_template($r, "footer",
+               { version => $Sesse::pr0n::Common::VERSION });
+}
+
+sub scale_aspect {
+       my ($width, $height, $thumbxres, $thumbyres) = @_;
+
+       unless ($thumbxres >= $width &&
+               $thumbyres >= $height) {
+               my $sfh = $width / $thumbxres;
+               my $sfv = $height / $thumbyres;
+               if ($sfh > $sfv) {
+                       $width  /= $sfh;
+                       $height /= $sfh;
+               } else {
+                       $width  /= $sfv;
+                       $height /= $sfv;
+               }
+               $width = POSIX::floor($width);
+               $height = POSIX::floor($height);
+       }
+
+       return ($width, $height);
+}
+
+sub print_link {
+       my ($r, $title, $baseurl, $param, $defparam) = @_;
+       my $str = "<a href=\"$baseurl";
+       my $first = 1;
+
+       while (my ($key, $value) = each %$param) {
+               next unless defined($value);
+               next if (defined($defparam->{$key}) && $value == $defparam->{$key});
+       
+               $str .= ($first) ? "?" : '&amp;';
+               $str .= "$key=$value";
+               $first = 0;
+       }
+       
+       $str .= "\">$title</a>";
+       $r->print($str);
+}
+
+sub get_dbh {
+       # Check that we are alive
+       if (!(defined($dbh) && $dbh->ping)) {
+               # Try to reconnect
+               Apache2::ServerUtil->server->log_error("Lost contact with PostgreSQL server, trying to reconnect...");
+               unless ($dbh = DBI->connect("dbi:Pg:dbname=pr0n;host=127.0.0.1", "pr0n", "EsVdwImY")) {
+                       $dbh = undef;
+                       die "Couldn't connect to PostgreSQL database";
+               }
+       }
+
+       return $dbh;
+}
+
+sub get_base {
+       my $r = shift;
+       return $r->dir_config('ImageBase');
+}
+
+sub get_disk_location {
+       my ($r, $id) = @_;
+        my $dir = POSIX::floor($id / 256);
+       return get_base($r) . "images/$dir/$id.jpg";
+}
+
+sub get_cache_location {
+       my ($r, $id, $width, $height, $infobox) = @_;
+        my $dir = POSIX::floor($id / 256);
+
+       if ($infobox) {
+               return get_base($r) . "cache/$dir/$id-$width-$height.jpg";
+       } else {
+               return get_base($r) . "cache/$dir/$id-$width-$height-nobox.jpg";
+       }
+}
+
+sub update_width_height {
+       my ($r, $id, $width, $height) = @_;
+
+       # Also find the date taken if appropriate (from the EXIF tag etc.)
+       my $info = Image::ExifTool::ImageInfo(get_disk_location($r, $id));
+       my $datetime = undef;
+
+       if (defined($info->{'DateTimeOriginal'})) {
+               # Parse the date and time over to ISO format
+               if ($info->{'DateTimeOriginal'} =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/ && $1 > 1990) {
+                       $datetime = "$1-$2-$3 $4:$5:$6";
+               }
+       }
+
+       $dbh->do('UPDATE images SET width=?, height=?, date=? WHERE id=?',
+                undef, $width, $height, $datetime, $id)
+               or die "Couldn't update width/height in SQL: $!";
+
+       # update the last_picture cache as well (this should of course be done
+       # via a trigger, but this is less complicated :-) )
+       $dbh->do('UPDATE events SET last_picture=(SELECT COALESCE(MAX(date),\'1970-01-01 00:00:00\') FROM images WHERE event=events.id) WHERE id=(SELECT event FROM images WHERE id=?)',
+               undef, $id)
+               or die "Couldn't update last_picture in SQL: $!";
+}
+
+sub check_access {
+       my $r = shift;
+
+       my $auth = $r->headers_in->{'authorization'};
+       if (!defined($auth) || $auth !~ m#^Basic ([a-zA-Z0-9+/]+=*)$#) {
+               $r->content_type('text/plain; charset=utf-8');
+               $r->status(401);
+               $r->headers_out->{'www-authenticate'} = 'Basic realm="pr0n.sesse.net"';
+               $r->print("Need authorization\n");
+               return undef;
+       }
+       
+       #return qw(sesse Sesse);
+
+       my ($user, $pass) = split /:/, MIME::Base64::decode_base64($1);
+       # WinXP is stupid :-)
+       if ($user =~ /^.*\\(.*)$/) {
+               $user = $1;
+       }
+
+       my $takenby;
+       if ($user =~ /^([a-zA-Z0-9^_-]+)\@([a-zA-Z0-9^_-]+)$/) {
+               $user = $1;
+               $takenby = $2;
+       } else {
+               ($takenby = $user) =~ s/^([a-zA-Z])/uc($1)/e;
+       }
+       
+       my $oldpass = $pass;
+       $pass = Digest::SHA1::sha1_base64($pass);
+       my $ref = $dbh->selectrow_hashref('SELECT count(*) AS auth FROM users WHERE username=? AND sha1password=? AND vhost=?',
+               undef, $user, $pass, $r->get_server_name);
+       if ($ref->{'auth'} != 1) {
+               $r->content_type('text/plain; charset=utf-8');
+               warn "No user exists, only $auth";
+               $r->status(401);
+               $r->headers_out->{'www-authenticate'} = 'Basic realm="pr0n.sesse.net"';
+               $r->print("Authorization failed");
+               $r->log->warn("Authentication failed for $user/$takenby");
+               return undef;
+       }
+
+       $r->log->info("Authentication succeeded for $user/$takenby");
+
+       return ($user, $takenby);
+}
+       
+sub stat_image {
+       my ($r, $event, $filename) = (@_);
+       my $ref = $dbh->selectrow_hashref(
+               'SELECT id FROM images WHERE event=? AND filename=?',
+               undef, $event, $filename);
+       if (!defined($ref)) {
+               return (undef, undef, undef);
+       }
+       return stat_image_from_id($r, $ref->{'id'});
+}
+
+sub stat_image_from_id {
+       my ($r, $id) = @_;
+
+       my $fname = get_disk_location($r, $id);
+       my (undef, undef, undef, undef, undef, undef, undef, $size, undef, $mtime) = stat($fname)
+               or return (undef, undef, undef);
+
+       return ($fname, $size, $mtime);
+}
+
+sub ensure_cached {
+       my ($r, $filename, $id, $dbwidth, $dbheight, $infobox, $xres, $yres, @otherres) = @_;
+
+       my $fname = get_disk_location($r, $id);
+       unless (defined($xres) && ($xres < $dbheight || $yres < $dbwidth || $dbwidth == -1 || $dbheight == -1)) {
+               return ($fname, 0);
+       }
+
+       my $cachename = get_cache_location($r, $id, $xres, $yres, $infobox);
+       if (! -r $cachename or (-M $cachename > -M $fname)) {
+               # If we are in overload mode (aka Slashdot mode), refuse to generate
+               # new thumbnails.
+               if (Sesse::pr0n::Overload::is_in_overload($r)) {
+                       $r->log->warn("In overload mode, not scaling $id to $xres x $yres");
+                       error($r, 'System is in overload mode, not doing any scaling');
+               }
+       
+               # Need to generate the cache; read in the image
+               my $magick = new Image::Magick;
+               my $info = Image::ExifTool::ImageInfo($fname);
+
+               # NEF files aren't autodetected
+               $fname = "NEF:$fname" if ($filename =~ /\.nef$/i);
+               $r->log->warn("Generating $fname for $filename");
+               
+               my $err = $magick->Read($fname);
+               if ($err) {
+                       $r->log->warn("$fname: $err");
+                       $err =~ /(\d+)/;
+                       if ($1 >= 400) {
+                               undef $magick;
+                               error($r, "$fname: $err");
+                       }
+               }
+
+               # If we use ->[0] unconditionally, text rendering (!) seems to crash
+               my $img = (scalar @$magick > 1) ? $magick->[0] : $magick;
+
+               my $width = $img->Get('columns');
+               my $height = $img->Get('rows');
+
+               # Update the SQL database if it doesn't contain the required info
+               if ($dbwidth == -1 || $dbheight == -1) {
+                       $r->log->info("Updating width/height for $id: $width x $height");
+                       update_width_height($r, $id, $width, $height);
+               }
+                       
+               # We always want RGB JPEGs
+               if ($img->Get('Colorspace') eq "CMYK") {
+                       $img->Set(colorspace=>'RGB');
+               }
+
+               while (defined($xres) && defined($yres)) {
+                       my ($nxres, $nyres) = (shift @otherres, shift @otherres);
+                       my $cachename = get_cache_location($r, $id, $xres, $yres, $infobox);
+                       
+                       my $cimg;
+                       if (defined($nxres) && defined($nyres)) {
+                               # we have more resolutions to scale, so don't throw
+                               # the image away
+                               $cimg = $img->Clone();
+                       } else {
+                               $cimg = $img;
+                       }
+               
+                       my ($nwidth, $nheight) = scale_aspect($width, $height, $xres, $yres);
+
+                       # Use lanczos (sharper) for heavy scaling, mitchell (faster) otherwise
+                       my $filter = 'Mitchell';
+                       my $quality = 90;
+
+                       if ($width / $nwidth > 8.0 || $height / $nheight > 8.0) {
+                               $filter = 'Lanczos';
+                               $quality = 80;
+                       }
+
+                       $cimg->Resize(width=>$nwidth, height=>$nheight, filter=>$filter);
+
+                       if (($nwidth >= 800 || $nheight >= 600) && $infobox == 1) {
+                               make_infobox($cimg, $info, $r);
+                       }
+
+                       # Strip EXIF tags etc.
+                       $cimg->Strip();
+
+                       $err = $cimg->write(filename=>$cachename, quality=>$quality);
+
+                       undef $cimg;
+
+                       ($xres, $yres) = ($nxres, $nyres);
+
+                       $r->log->info("New cache: $nwidth x $nheight for $id.jpg");
+               }
+               
+               undef $magick;
+               undef $img;
+               if ($err) {
+                       $r->log->warn("$fname: $err");
+                       $err =~ /(\d+)/;
+                       if ($1 >= 400) {
+                               @$magick = ();
+                               error($r, "$fname: $err");
+                       }
+               }
+       }
+       return ($cachename, 1);
+}
+
+sub get_mimetype_from_filename {
+       my $filename = shift;
+       my MIME::Type $type = $mimetypes->mimeTypeOf($filename);
+       $type = "image/jpeg" if (!defined($type));
+       return $type;
+}
+
+sub make_infobox {
+       my ($img, $info, $r) = @_;
+       
+       my @lines = ();
+       my @classic_fields = ();
+       
+       if (defined($info->{'DateTimeOriginal'}) &&
+           $info->{'DateTimeOriginal'} =~ /^(\d{4}):(\d\d):(\d\d) (\d\d):(\d\d):(\d\d)$/
+           && $1 >= 1990) {
+               push @lines, "$1-$2-$3 $4:$5";
+       }
+
+       push @lines, $info->{'Model'} if (defined($info->{'Model'}));
+       
+       # classic fields
+       if (defined($info->{'FocalLength'}) && $info->{'FocalLength'} =~ /^(\d+)(?:\.\d+)?(?:mm)?$/) {
+               push @classic_fields, ($1 . "mm");
+       } elsif (defined($info->{'FocalLength'}) && $info->{'FocalLength'} =~ /^(\d+)\/(\d+)$/) {
+               push @classic_fields, (sprintf "%.1fmm", ($1/$2));
+       }
+       if (defined($info->{'ExposureTime'}) && $info->{'ExposureTime'} =~ /^(\d+)\/(\d+)$/) {
+               my ($a, $b) = ($1, $2);
+               my $gcd = gcd($a, $b);
+               push @classic_fields, ($a/$gcd . "/" . $b/$gcd . "s");
+       }
+       if (defined($info->{'FNumber'}) && $info->{'FNumber'} =~ /^(\d+)\/(\d+)$/) {
+               my $f = $1/$2;
+               if ($f >= 10) {
+                       push @classic_fields, (sprintf "f/%.0f", $f);
+               } else {
+                       push @classic_fields, (sprintf "f/%.1f", $f);
+               }
+       } elsif (defined($info->{'FNumber'}) && $info->{'FNumber'} =~ /^(\d+)\.(\d+)$/) {
+               my $f = $info->{'FNumber'};
+               if ($f >= 10) {
+                       push @classic_fields, (sprintf "f/%.0f", $f);
+               } else {
+                       push @classic_fields, (sprintf "f/%.1f", $f);
+               }
+       }
+
+#      Apache2::ServerUtil->server->log_error(join(':', keys %$info));
+
+       if (defined($info->{'NikonD1-ISOSetting'})) {
+               push @classic_fields, $info->{'NikonD1-ISOSetting'}->[1] . " ISO";
+       } elsif (defined($info->{'ISOSetting'})) {
+               push @classic_fields, $info->{'ISOSetting'} . " ISO";
+       }
+
+       push @classic_fields, $info->{'ExposureBiasValue'} . " EV" if (defined($info->{'ExposureBiasValue'}) && $info->{'ExposureBiasValue'} != 0);
+       
+       if (scalar @classic_fields > 0) {
+               push @lines, join(', ', @classic_fields);
+       }
+
+       if (defined($info->{'Flash'})) {
+               if ($info->{'Flash'} =~ /did not fire/ || $info->{'Flash'} =~ /No Flash/) {
+                       push @lines, "No flash";
+               } elsif ($info->{'Flash'} =~ /fired/) {
+                       push @lines, "Flash";
+               } else {
+                       push @lines, $info->{'Flash'};
+               }
+       }
+
+       return if (scalar @lines == 0);
+
+       # OK, this sucks. Let's make something better :-)
+       @lines = ( join(" - ", @lines) );
+
+       # Find the required width
+       my $th = 14 * (scalar @lines) + 6;
+       my $tw = 1;
+
+       for my $line (@lines) {
+               my $this_w = ($img->QueryFontMetrics(text=>$line, font=>'/usr/share/fonts/truetype/msttcorefonts/Arial.ttf', pointsize=>12))[4];
+               $tw = $this_w if ($this_w >= $tw);
+       }
+
+       $tw += 6;
+
+       # Round up so we hit exact DCT blocks
+       $tw += 8 - ($tw % 8) unless ($tw % 8 == 0);
+       $th += 8 - ($th % 8) unless ($th % 8 == 0);
+       
+       return if ($tw > $img->Get('columns'));
+
+#      my $x = $img->Get('columns') - 8 - $tw;
+#      my $y = $img->Get('rows') - 8 - $th;
+       my $x = 0;
+       my $y = $img->Get('rows') - $th;
+       $tw = $img->Get('columns');
+
+       $x -= $x % 8;
+       $y -= $y % 8;
+
+       my $points = sprintf "%u,%u %u,%u", $x, $y, ($x+$tw-1), ($img->Get('rows') - 1);
+       my $lpoints = sprintf "%u,%u %u,%u", $x, $y, ($x+$tw-1), $y;
+#      $img->Draw(primitive=>'rectangle', stroke=>'black', fill=>'white', points=>$points);
+       $img->Draw(primitive=>'rectangle', stroke=>'white', fill=>'white', points=>$points);
+       $img->Draw(primitive=>'line', stroke=>'black', points=>$lpoints);
+
+       my $i = -(scalar @lines - 1)/2.0;
+       my $xc = $x + $tw / 2 - $img->Get('columns')/2;
+       my $yc = ($y + $img->Get('rows'))/2 - $img->Get('rows')/2;
+       #my $yc = ($y + $img->Get('rows'))/4;
+       my $yi = $th / (scalar @lines);
+       
+       $lpoints = sprintf "%u,%u %u,%u", $x, $yc + $img->Get('rows')/2, ($x+$tw-1), $yc+$img->Get('rows')/2;
+
+       for my $line (@lines) {
+               $img->Annotate(text=>$line, font=>'/usr/share/fonts/truetype/msttcorefonts/Arial.ttf', pointsize=>12, gravity=>'Center',
+               # $img->Annotate(text=>$line, font=>'Helvetica', pointsize=>12, gravity=>'Center',
+                       x=>int($xc), y=>int($yc + $i * $yi));
+       
+               $i = $i + 1;
+       }
+}
+
+sub gcd {
+       my ($a, $b) = @_;
+       return $a if ($b == 0);
+       return gcd($b, $a % $b);
+}
+
+1;
+
+
diff --git a/perl/Sesse/pr0n/Image.pm b/perl/Sesse/pr0n/Image.pm
new file mode 100644 (file)
index 0000000..30bf736
--- /dev/null
@@ -0,0 +1,85 @@
+package Sesse::pr0n::Image;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Common qw(error dberror);
+use POSIX;
+
+sub handler {
+       my $r = shift;
+       my $dbh = Sesse::pr0n::Common::get_dbh();
+
+#      if ($r->connection->remote_ip() eq '80.212.251.227') {
+#              die "Har du lest FAQen?";
+#      }
+
+       # Find the event and file name
+       my ($event,$filename,$xres,$yres);
+       my $infobox = 1;
+       if ($r->uri =~ m#^/([a-zA-Z0-9-]+)/([a-zA-Z0-9._-]+)$#) {
+               $event = $1;
+               $filename = $2;
+       } elsif ($r->uri =~ m#^/([a-zA-Z0-9-]+)/(\d+)x(\d+)/(nobox/)?([a-zA-Z0-9._-]+)$#) {
+               $event = $1;
+               $filename = $5;
+               $xres = $2;
+               $yres = $3;
+               $infobox = 0 if (defined($4));
+       }
+
+       my ($id, $dbwidth, $dbheight);
+       if ($event eq 'single' && $filename =~ /^(\d+)\.jpeg$/) {
+               $id = $1;
+       } else {
+               # Alas, we obviously need to do this :-)
+               # my $evq = $dbh->prepare('SELECT count(*) AS numev FROM events WHERE id=? AND vhost=?')
+               # or die "prepare(): $!";
+               # my $ref = $dbh->selectrow_hashref($evq, undef, $event, $r->get_server_name)
+               #       or dberror($r, "Could not look up $event");
+               # $ref->{'numev'} == 1
+               #       or error($r, "Could not find $event", 404, "File not found");
+       
+               # Look it up in the database
+               my $ref = $dbh->selectrow_hashref('SELECT id,width,height FROM images WHERE event=? AND filename=?',
+                       undef, $event, $filename);
+               error($r, "Could not find $event/$filename", 404, "File not found") unless (defined($ref));
+
+               $id = $ref->{'id'};
+               $dbwidth = $ref->{'width'};
+               $dbheight = $ref->{'height'};
+       }
+               
+       $dbwidth = -1 unless defined($dbwidth);
+       $dbheight = -1 unless defined($dbheight);
+
+       # Scale if we need to do so
+       my ($fname,$thumbnail) = Sesse::pr0n::Common::ensure_cached($r, $filename, $id, $dbwidth, $dbheight, $infobox, $xres, $yres);
+
+       # Output the image to the user
+       my $mime_type;
+       if ($thumbnail) {
+               $mime_type = "image/jpeg";
+       } else {
+               $mime_type = Sesse::pr0n::Common::get_mimetype_from_filename($filename);
+       }
+       $r->content_type($mime_type);
+       
+       my (undef, undef, undef, undef, undef, undef, undef, $size, undef, $mtime) = stat($fname)
+                or error($r, "stat of $fname: $!");
+               
+       $r->set_content_length($size);
+       $r->set_last_modified($mtime);
+
+       # If the client can use cache, by all means do so
+       if ((my $rc = $r->meets_conditions) != Apache2::Const::OK) {
+               return $rc;
+       }
+
+       $r->sendfile($fname);
+
+       return Apache2::Const::OK;
+}
+
+1;
+
+
diff --git a/perl/Sesse/pr0n/Index.pm b/perl/Sesse/pr0n/Index.pm
new file mode 100644 (file)
index 0000000..1782453
--- /dev/null
@@ -0,0 +1,454 @@
+package Sesse::pr0n::Index;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Common qw(error dberror);
+use Apache2::Request;
+use POSIX;
+
+sub handler {
+       my $r = shift;
+       my $apr = Apache2::Request->new($r);
+       my $dbh = Sesse::pr0n::Common::get_dbh();
+
+       # Find the event
+       $r->uri =~ m#^/([a-zA-Z0-9-]+)/?$#
+               or error($r, "Could not extract event");
+       my $event = $1;
+
+       # Fix common error: pr0n.sesse.net/event -> pr0n.sesse.net/event/
+       if ($r->uri !~ m#/$#) {
+               $r->headers_out->{'location'} = "/$event/";
+               return Apache2::Const::REDIRECT;
+       }
+
+       # Internal? (Ugly?) 
+       if ($r->get_server_name =~ /internal/) {
+               my $user = Sesse::pr0n::Common::check_access($r);
+               if (!defined($user)) {
+                       return Apache2::Const::OK;
+               }
+       }
+
+       # Read the appropriate settings from the query string into the settings hash
+       my %defsettings = (
+               thumbxres => 80,
+               thumbyres => 64,
+               xres => undef,
+               yres => undef,
+               start => 1,
+               num => -1,
+               all => 1,
+               infobox => 1,
+               rot => 0,
+               sel => 0,
+       );
+       
+       # Reduce the front page load when in overload mode.
+       if (Sesse::pr0n::Overload::is_in_overload($r)) {
+               $defsettings{'num'} = 100;
+       }
+               
+       my %settings = %defsettings;
+
+       for my $s qw(thumbxres thumbyres xres yres start num all infobox rot sel) {
+               my $val = $apr->param($s);
+               if (defined($val) && $val =~ /^(\d+)$/) {
+                       $settings{$s} = $val;
+               }
+               if ($s eq "num" && defined($val) && $val == -1) {
+                       $settings{$s} = $val;
+               }
+       }
+
+       my $thumbxres = $settings{'thumbxres'};
+       my $thumbyres = $settings{'thumbyres'};
+       my $xres = $settings{'xres'};
+       my $yres = $settings{'yres'};
+       my $start = $settings{'start'};
+       my $num = $settings{'num'};
+       my $all = $settings{'all'};
+       my $infobox = $settings{'infobox'} ? '' : 'nobox/';
+       my $rot = $settings{'rot'};
+       my $sel = $settings{'sel'};
+
+       if (defined($num) && $num == -1) {
+               $num = undef;
+       }
+
+       my $ref = $dbh->selectrow_hashref('SELECT * FROM events WHERE id=? AND vhost=?',
+               undef, $event, $r->get_server_name)
+               or error($r, "Could not find event $event", 404, "File not found");
+
+       my $name = $ref->{'name'};
+       my $date = $ref->{'date'};
+       
+       # Count the number of selected images.
+       $ref = $dbh->selectrow_hashref("SELECT COUNT(*) AS num_selected FROM images WHERE event=? AND selected=\'t\'", undef, $event);
+       my $num_selected = $ref->{'num_selected'};
+
+       # Find all images related to this event.
+       my $q;
+       my $where = ($all == 0) ? ' AND selected=\'t\'' : '';
+
+       if (defined($start) && defined($num)) {
+               $q = $dbh->prepare("SELECT *, (date - INTERVAL '6 hours')::date AS day FROM images WHERE event=? $where ORDER BY (date - INTERVAL '6 hours')::date,takenby,date,filename LIMIT $num OFFSET " . ($start-1))
+                       or dberror($r, "prepare()");
+       } else {
+               $q = $dbh->prepare("SELECT *, (date - INTERVAL '6 hours')::date AS day FROM images WHERE event=? $where ORDER BY (date - INTERVAL '6 hours')::date,takenby,date,filename")
+                       or dberror($r, "prepare()");
+       }
+       $q->execute($event)
+               or dberror($r, "image enumeration");
+
+       # Print the page itself
+       Sesse::pr0n::Common::header($r, "$name [$event]");
+       Sesse::pr0n::Templates::print_template($r, "date", { date => $date });
+
+       if (Sesse::pr0n::Overload::is_in_overload($r)) {
+               Sesse::pr0n::Templates::print_template($r, "overloadmode");
+       }
+
+       print_thumbsize($r, $event, \%settings, \%defsettings);
+       print_viewres($r, $event, \%settings, \%defsettings);
+       print_pagelimit($r, $event, \%settings, \%defsettings);
+       print_infobox($r, $event, \%settings, \%defsettings);
+       print_nextprev($r, $event, \%settings, \%defsettings);
+       print_selected($r, $event, \%settings, \%defsettings) if ($num_selected > 0);
+
+       my $toclose = 0;
+       my $lastupl = "";
+       
+       # Print out all thumbnails
+       if ($rot == 1) {
+               $r->print("    <form method=\"post\" action=\"/rotate\">\n");
+       
+               while (my $ref = $q->fetchrow_hashref()) {
+                       my $imgsz = "";
+                       my $takenby = $ref->{'takenby'};
+                       if (defined($ref->{'day'})) {
+                                $takenby .= ", " . $ref->{'day'};
+                       }
+
+                       if ($takenby ne $lastupl) {
+                               $lastupl = $takenby;
+                               Sesse::pr0n::Templates::print_template($r, "submittedby", { author => $lastupl });
+                       }
+                       if ($ref->{'width'} != -1 && $ref->{'height'} != -1) {
+                               my $width = $ref->{'width'};
+                               my $height = $ref->{'height'};
+                                       
+                               ($width, $height) = Sesse::pr0n::Common::scale_aspect($width, $height, $thumbxres, $thumbyres);
+                               $imgsz = " width=\"$width\" height=\"$height\"";
+                       }
+
+                       my $filename = $ref->{'filename'};
+                       my $uri = $filename;
+                       if (defined($xres) && defined($yres)) {
+                               $uri = "${xres}x$yres/$infobox$filename";
+                       }
+
+                       $r->print("    <p><a href=\"$uri\"><img src=\"${thumbxres}x${thumbyres}/$filename\" alt=\"\"$imgsz /></a>\n");
+                       $r->print("      90 <input type=\"checkbox\" name=\"rot-" .
+                               $ref->{'id'} . "-90\" />\n");
+                       $r->print("      180 <input type=\"checkbox\" name=\"rot-" .
+                               $ref->{'id'} . "-180\" />\n");
+                       $r->print("      270 <input type=\"checkbox\" name=\"rot-" .
+                               $ref->{'id'} . "-270\" />\n");
+                       $r->print("      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" .
+                               "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Del <input type=\"checkbox\" name=\"del-" . $ref->{'id'} . "\" /></p>\n");
+               }
+               $r->print("      <input type=\"submit\" value=\"Rotate\" />\n");
+               $r->print("    </form>\n");
+       } elsif ($sel == 1) {
+               $r->print("    <form method=\"post\" action=\"/select\">\n");
+               $r->print("      <input type=\"hidden\" name=\"event\" value=\"$event\" />\n");
+       
+               while (my $ref = $q->fetchrow_hashref()) {
+                       my $imgsz = "";
+                       my $takenby = $ref->{'takenby'};
+                       if (defined($ref->{'day'})) {
+                                $takenby .= ", " . $ref->{'day'};
+                       }
+
+                       if ($takenby ne $lastupl) {
+                               $lastupl = $takenby;
+                               Sesse::pr0n::Templates::print_template($r, "submittedby", { author => $lastupl });
+                       }
+                       if ($ref->{'width'} != -1 && $ref->{'height'} != -1) {
+                               my $width = $ref->{'width'};
+                               my $height = $ref->{'height'};
+                                       
+                               ($width, $height) = Sesse::pr0n::Common::scale_aspect($width, $height, $thumbxres, $thumbyres);
+                               $imgsz = " width=\"$width\" height=\"$height\"";
+                       }
+
+                       my $filename = $ref->{'filename'};
+                       my $uri = $filename;
+                       if (defined($xres) && defined($yres)) {
+                               $uri = "${xres}x$yres/$infobox$filename";
+                       }
+
+                       my $selected = $ref->{'selected'} ? ' checked="checked"' : '';
+
+                       $r->print("    <p><a href=\"$uri\"><img src=\"${thumbxres}x${thumbyres}/$filename\" alt=\"\"$imgsz /></a>\n");
+                       $r->print("      <input type=\"checkbox\" name=\"sel-" .
+                               $ref->{'id'} . "\"$selected /></p>\n");
+               }
+               $r->print("      <input type=\"submit\" value=\"Select\" />\n");
+               $r->print("    </form>\n");
+       } else {
+               while (my $ref = $q->fetchrow_hashref()) {
+                       my $imgsz = "";
+                       my $takenby = $ref->{'takenby'};
+                       if (defined($ref->{'day'})) {
+                                $takenby .= ", " . $ref->{'day'};
+                       }
+
+                       if ($takenby ne $lastupl) {
+                               $r->print("    </p>\n\n") if ($lastupl ne "");
+                               $lastupl = $takenby;
+                               Sesse::pr0n::Templates::print_template($r, "submittedby", { author => $lastupl });
+                               $r->print("    <p>\n");
+                       }
+                       if ($ref->{'width'} != -1 && $ref->{'height'} != -1) {
+                               my $width = $ref->{'width'};
+                               my $height = $ref->{'height'};
+                                       
+                               ($width, $height) = Sesse::pr0n::Common::scale_aspect($width, $height, $thumbxres, $thumbyres);
+                               $imgsz = " width=\"$width\" height=\"$height\"";
+                       }
+
+                       my $filename = $ref->{'filename'};
+                       my $uri = $filename;
+                       if (defined($xres) && defined($yres)) {
+                               $uri = "${xres}x$yres/$infobox$filename";
+                       }
+                       
+                       $r->print("      <a href=\"$uri\"><img src=\"${thumbxres}x${thumbyres}/$filename\" alt=\"\"$imgsz /></a>\n");
+               }
+               $r->print("    </p>\n");
+       }
+
+       print_nextprev($r, $event, \%settings, \%defsettings);
+       Sesse::pr0n::Common::footer($r);
+
+       return Apache2::Const::OK;
+}
+
+sub eq_with_undef {
+       my ($a, $b) = @_;
+       
+       return 1 if (!defined($a) && !defined($b));
+       return 0 unless (defined($a) && defined($b));
+       return ($a eq $b);
+}
+
+sub print_changes {
+       my ($r, $event, $template, $settings, $defsettings, $var1, $var2, $alternatives) = @_;
+
+       my $title = Sesse::pr0n::Templates::fetch_template($r, $template);
+       chomp $title;
+       $r->print("    <p>$title:\n");
+
+       for my $a (@$alternatives) {
+               # Parse the current alternative
+               my ($v1, $v2) = split /x/, $a;
+               my %newsettings = %$settings;
+
+               if (defined($v1) && defined($v2)) {
+                       $newsettings{$var1} = $v1;
+                       $newsettings{$var2} = $v2;
+               } else {
+                       $newsettings{$var1} = undef;
+                       $newsettings{$var2} = undef;
+               }
+
+               $r->print("      ");
+
+               # Check if these settings are current (print only label)
+               if (eq_with_undef($settings->{$var1}, $newsettings{$var1}) &&
+                   eq_with_undef($settings->{$var2}, $newsettings{$var2})) {
+                       $r->print($a);
+               } else {
+                       Sesse::pr0n::Common::print_link($r, $a, "/$event/", \%newsettings, $defsettings);
+               }
+               $r->print("\n");
+       }
+       $r->print("    </p>\n");
+}
+
+sub print_thumbsize {
+       my ($r, $event, $settings, $defsettings) = @_;
+       my @alternatives = qw(80x64 120x96 160x128 240x192 320x256);
+
+       print_changes($r, $event, 'thumbsize', $settings, $defsettings,
+                     'thumbxres', 'thumbyres', \@alternatives);
+}
+sub print_viewres {
+       my ($r, $event, $settings, $defsettings) = @_;
+       my @alternatives = qw(320x256 512x384 640x480 800x600 1024x768 1280x960);
+       chomp (my $unlimited = Sesse::pr0n::Templates::fetch_template($r, 'viewres-unlimited'));
+       push @alternatives, $unlimited;
+
+       print_changes($r, $event, 'viewres', $settings, $defsettings,
+                     'xres', 'yres', \@alternatives);
+}
+
+sub print_pagelimit {
+       my ($r, $event, $settings, $defsettings) = @_;
+       
+       my $title = Sesse::pr0n::Templates::fetch_template($r, 'imgsperpage');
+       chomp $title;
+       $r->print("    <p>$title:\n");
+       
+       # Get choices
+       chomp (my $unlimited = Sesse::pr0n::Templates::fetch_template($r, 'imgsperpage-unlimited'));
+       my @alternatives = qw(10 50 100 500);
+       push @alternatives, $unlimited;
+       
+       for my $num (@alternatives) {
+               my %newsettings = %$settings;
+
+               if ($num !~ /^\d+$/) { # unlimited
+                       $newsettings{'num'} = -1;
+               } else {
+                       $newsettings{'num'} = $num;
+               }
+
+               $r->print("      ");
+               if (eq_with_undef($settings->{'num'}, $newsettings{'num'})) {
+                       $r->print($num);
+               } else {
+                       Sesse::pr0n::Common::print_link($r, $num, "/$event/", \%newsettings, $defsettings);
+               }
+               $r->print("\n");
+       }
+       $r->print("    </p>\n");
+}
+
+sub print_infobox {
+       my ($r, $event, $settings, $defsettings) = @_;
+
+       chomp (my $title = Sesse::pr0n::Templates::fetch_template($r, 'infobox'));
+       chomp (my $on = Sesse::pr0n::Templates::fetch_template($r, 'infobox-on'));
+       chomp (my $off = Sesse::pr0n::Templates::fetch_template($r, 'infobox-off'));
+
+        $r->print("    <p>$title:\n");
+
+       my %newsettings = %$settings;
+
+       if ($settings->{'infobox'} == 1) {
+               $r->print($on);
+       } else {
+               $newsettings{'infobox'} = 1;
+               Sesse::pr0n::Common::print_link($r, $on, "/$event/", \%newsettings, $defsettings);
+       }
+
+       $r->print(' ');
+
+       if ($settings->{'infobox'} == 0) {
+               $r->print($off);
+       } else {
+               $newsettings{'infobox'} = 0;
+               Sesse::pr0n::Common::print_link($r, $off, "/$event/", \%newsettings, $defsettings);
+       }
+       
+       $r->print('</p>');
+}
+
+sub print_nextprev {
+       my ($r, $event, $settings, $defsettings) = @_;
+       my $start = $settings->{'start'};
+       my $num = $settings->{'num'};
+       my $dbh = Sesse::pr0n::Common::get_dbh();
+
+       $num = undef if (defined($num) && $num == -1);
+       return unless (defined($start) && defined($num));
+
+       # determine total number
+       my $ref = $dbh->selectrow_hashref('SELECT count(*) AS num_images FROM images WHERE event=?',
+               undef, $event)
+               or dberror($r, "image enumeration");
+       my $num_images = $ref->{'num_images'};
+
+       return if ($start == 1 && $start + $num >= $num_images);
+
+       my $end = $start + $num - 1;
+       if ($end > $num_images) {
+               $end = $num_images;
+       }
+
+       $r->print("    <p>\n");
+
+       # Previous
+       if ($start > 1) {
+               my $newstart = $start - $num;
+               if ($newstart < 1) {
+                       $newstart = 1;
+               }
+               my $newend = $newstart + $num - 1;
+               if ($newend > $num_images) {
+                       $newend = $num_images;
+               }
+
+               my %newsettings = %$settings;
+               $newsettings{'start'} = $newstart;
+               chomp (my $title = Sesse::pr0n::Templates::fetch_template($r, 'prevpage'));
+               Sesse::pr0n::Common::print_link($r, "$title ($newstart-$newend)\n", "/$event/", \%newsettings, $defsettings);
+       }
+
+       # This
+       chomp (my $title = Sesse::pr0n::Templates::fetch_template($r, 'thispage'));
+       $r->print("    $title ($start-$end)\n");
+
+       # Next
+       if ($end < $num_images) {
+               my $newstart = $start + $num;
+               my $newend = $newstart + $num - 1;
+               if ($newend > $num_images) {
+                       $newend = $num_images;
+               }
+               
+               my %newsettings = %$settings;
+               $newsettings{'start'} = $newstart;
+               chomp (my $title = Sesse::pr0n::Templates::fetch_template($r, 'nextpage'));
+               Sesse::pr0n::Common::print_link($r, "$title ($newstart-$newend)", "/$event/", \%newsettings, $defsettings);
+       }
+
+       $r->print("    </p>\n");
+}
+
+sub print_selected {
+       my ($r, $event, $settings, $defsettings) = @_;
+
+       chomp (my $title = Sesse::pr0n::Templates::fetch_template($r, 'show'));
+       chomp (my $all = Sesse::pr0n::Templates::fetch_template($r, 'show-all'));
+       chomp (my $sel = Sesse::pr0n::Templates::fetch_template($r, 'show-selected'));
+
+        $r->print("    <p>$title:\n");
+
+       my %newsettings = %$settings;
+
+       if ($settings->{'all'} == 0) {
+               $r->print($sel);
+       } else {
+               $newsettings{'all'} = 0;
+               Sesse::pr0n::Common::print_link($r, $sel, "/$event/", \%newsettings, $defsettings);
+       }
+
+       $r->print(' ');
+
+       if ($settings->{'all'} == 1) {
+               $r->print($all);
+       } else {
+               $newsettings{'all'} = 1;
+               Sesse::pr0n::Common::print_link($r, $all, "/$event/", \%newsettings, $defsettings);
+       }
+       
+       $r->print('</p>');
+}
+       
+1;
+
+
diff --git a/perl/Sesse/pr0n/Listing.pm b/perl/Sesse/pr0n/Listing.pm
new file mode 100644 (file)
index 0000000..8447040
--- /dev/null
@@ -0,0 +1,49 @@
+package Sesse::pr0n::Listing;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Common qw(error dberror);
+
+sub handler {
+       my $r = shift;
+       my $dbh = Sesse::pr0n::Common::get_dbh();
+
+        # Internal? (Ugly?)
+       if ($r->get_server_name =~ /internal/) {
+               my $user = Sesse::pr0n::Common::check_access($r);
+               if (!defined($user)) {
+                       return Apache2::Const::OK;
+               }
+       }
+
+#      my $q = $dbh->prepare('SELECT t1.id,t1.date,t1.name FROM events t1 LEFT JOIN images t2 ON t1.id=t2.event WHERE t1.vhost=? GROUP BY t1.id,t1.date,t1.name ORDER BY COALESCE(MAX(t2.date),\'1970-01-01 00:00:00\'),t1.id') or
+#              dberror($r, "Couldn't list events");
+       my $q = $dbh->prepare('SELECT id,date,name FROM events WHERE vhost=? ORDER BY last_picture DESC')
+               or dberror($r, "Couldn't list events");
+       $q->execute($r->get_server_name)
+               or dberror($r, "Couldn't get events");
+
+       $r->content_type('text/html; charset=utf-8');
+       $r->subprocess_env;
+
+       Sesse::pr0n::Common::header($r, Sesse::pr0n::Templates::fetch_template($r, 'event-listing'));
+       $r->print("    <ul>\n");
+
+       while (my $ref = $q->fetchrow_hashref()) {
+               my $id = $ref->{'id'};
+               my $date = $ref->{'date'};
+               my $name = $ref->{'name'};
+               
+               $r->print("      <li><a href=\"$id/\">$name</a> ($date)</li>\n");
+       }
+
+       $r->print("    </ul>\n");
+       Sesse::pr0n::Common::footer($r);
+
+       $q->finish();
+       return Apache2::Const::OK;
+}
+
+1;
+
+
diff --git a/perl/Sesse/pr0n/NewEvent.pm b/perl/Sesse/pr0n/NewEvent.pm
new file mode 100644 (file)
index 0000000..3eaa5dd
--- /dev/null
@@ -0,0 +1,55 @@
+package Sesse::pr0n::NewEvent;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Common qw(error dberror);
+use Apache2::Request;
+
+sub handler {
+       my $r = shift;
+       my $apr = Apache2::Request->new($r);
+       my $dbh = Sesse::pr0n::Common::get_dbh();
+       my $user = Sesse::pr0n::Common::check_access($r);
+       if (!defined($user)) {
+               return Apache2::Const::OK;
+       }
+
+       Sesse::pr0n::Common::header($r, "Legger til ny hendelse");
+
+       my $ok = 1;
+
+       my $id = $apr->param('id');
+       if (!defined($id) || $id =~ /^\s*$/ || $id !~ /^([a-zA-Z0-9-]+)$/) {
+               $r->print("    <p>Feil: Manglende eller ugyldig ID.</p>\n");
+               $ok = 0;
+       }
+
+       my $date = $apr->param('date');
+       if (!defined($date) || $date =~ /^\s*$/ || $date =~ /[<>&]/ || length($date) > 100) {
+               $r->print("    <p>Feil: Manglende eller ugyldig dato.</p>\n");
+               $ok = 0;
+       }
+       
+       my $desc = $apr->param('desc');
+       if (!defined($desc) || $desc =~ /^\s*$/ || $desc =~ /[<>&]/ || length($desc) > 100) {
+               $r->print("    <p>Feil: Manglende eller ugyldig beskrivelse.</p>\n");
+               $ok = 0;
+       }
+       
+       if ($ok == 0) {
+               $r->print("    <p>Rett opp i feilene over før du går videre.</p>\n");
+       } else {
+               $dbh->do("INSERT INTO events (id,date,name,vhost) VALUES (?,?,?,?)",
+                       undef, $id, $date, $desc, $r->get_server_name)
+                       or dberror($r, "Kunne ikke sette inn ny hendelse");
+               $r->print("    <p>Hendelsen '$id' lagt til.</p>");
+       }
+       
+       Sesse::pr0n::Common::footer($r);
+
+       return Apache2::Const::OK;
+}
+
+1;
+
+
diff --git a/perl/Sesse/pr0n/Overload.pm b/perl/Sesse/pr0n/Overload.pm
new file mode 100644 (file)
index 0000000..0adce7c
--- /dev/null
@@ -0,0 +1,70 @@
+# Note: This package is shared between server processes as much as we can,
+#       for obvious reasons (you don't want just half the server to go in
+#       overload mode if you can help it)
+
+package Sesse::pr0n::Overload;
+use strict;
+use warnings;
+
+BEGIN {
+       use Exporter ();
+       our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+       $VERSION     = 1.00;
+       @ISA         = qw(Exporter);
+       @EXPORT      = qw();
+       %EXPORT_TAGS = qw();
+       @EXPORT_OK   = qw();
+}
+our ($last_update, $loadavg, $in_overload);
+
+sub is_in_overload {
+       my $r = shift;
+
+       # Manually set overload mode
+       if (lc($r->dir_config('OverloadMode')) eq 'on') {
+               return 1;
+       }
+
+       # By default we are not in overload mode
+       if (!defined($in_overload)) {
+               $in_overload = 0;
+       }
+
+       my $enable_threshold = $r->dir_config('OverloadEnableThreshold') || 10.0;
+       my $disable_threshold = $r->dir_config('OverloadDisableThreshold') || 5.0;
+       
+       # Check if our load average estimate is more than a minute old
+       if (!defined($last_update) || (time - $last_update) > 60) {
+               open LOADAVG, "</proc/loadavg"
+                       or die "/proc/loadavg: $!";
+               my $line = <LOADAVG>;
+               close LOADAVG;
+               
+               $line =~ /^(\d+\.\d+) / or die "Couldn't parse /proc/loadavg";
+               
+               $loadavg = $1;
+               $last_update = time;
+
+               if ($in_overload) {
+                       if ($loadavg < $disable_threshold) {
+                               $r->log->info("Current load average is $loadavg (threshold: $disable_threshold), leaving overload mode");
+                               $in_overload = 0;
+                       } else {
+                               $r->log->warn("Current load average is $loadavg (threshold: $disable_threshold), staying in overload mode");
+                       }
+               } else {
+                       if ($loadavg > $enable_threshold) {
+                               $r->log->warn("Current load average is $loadavg (threshold: $enable_threshold), entering overload mode");
+                               $in_overload = 1;
+                       } else {
+                               $r->log->info("Current load average is $loadavg (threshold: $enable_threshold)");
+                       }
+               }
+       }
+
+       return $in_overload;
+}
+
+1;
+
diff --git a/perl/Sesse/pr0n/Rotate.pm b/perl/Sesse/pr0n/Rotate.pm
new file mode 100644 (file)
index 0000000..923e5df
--- /dev/null
@@ -0,0 +1,69 @@
+package Sesse::pr0n::Rotate;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Common qw(error dberror);
+use Apache2::Request;
+
+sub handler {
+       my $r = shift;
+       my $apr = Apache2::Request->new($r);
+       my $dbh = Sesse::pr0n::Common::get_dbh();
+       my ($user, $takenby) = Sesse::pr0n::Common::check_access($r);
+       if (!defined($user)) {
+               return Apache2::Const::OK;
+       }
+
+       Sesse::pr0n::Common::header($r, "Rotation/deletion results");
+
+       {
+               # Enable transactions and error raising temporarily
+               local $dbh->{AutoCommit} = 0;
+               local $dbh->{RaiseError} = 1;
+
+               my @params = $apr->param();
+               my $key;
+               for $key (@params) {
+                       # Rotation
+                       if ($key =~ /^rot-(\d+)-(90|180|270)$/ && $apr->param($key) eq 'on') {
+                               my ($id, $rotval) = ($1,$2);
+                               my $fname = Sesse::pr0n::Common::get_disk_location($r, $id);
+                               (my $tmpfname = $fname) =~ s/\.jpg$/-tmp.jpg/;
+
+                               system("/usr/bin/jpegtran -rotate $rotval -copy all < '$fname' > '$tmpfname' && mv '$tmpfname' '$fname'") == 0
+                                       or error($r, "Rotation of $id [/usr/bin/jpegtran -rotate $rotval -copy all < '$fname' > '$tmpfname' && mv '$tmpfname' '$fname'] failed: $!.");
+                               $r->print("    <p>Rotated image ID `$id' by $rotval degrees.</p>\n");
+
+                               if ($rotval == 90 || $rotval == 270) {
+                                       my $q = $dbh->do('UPDATE images SET height=width,width=height WHERE id=?', undef, $id)
+                                               or dberror($r, "Size clear of $id failed: $!");
+                               }
+                       } elsif ($key =~ /^del-(\d+)$/ && $apr->param($key) eq 'on') {
+                               my $id = $1;
+                               {
+
+                                       eval {
+                                               $dbh->do('INSERT INTO deleted_images SELECT * FROM images WHERE id=?',
+                                                               undef, $id);
+                                               $dbh->do('DELETE FROM images WHERE id=?',
+                                                               undef, $id);
+                                       };
+                                       if ($@) {
+# Some error occurred, rollback and bomb out
+                                               $dbh->rollback;
+                                               dberror($r, "Transaction aborted because $@");
+                                       }
+                               }
+                               $r->print("    <p>Deleted image `$id'.</p>\n");
+                       }
+               }
+       }
+
+       Sesse::pr0n::Common::footer($r);
+
+       return Apache2::Const::OK;
+}
+
+1;
+
+
diff --git a/perl/Sesse/pr0n/Select.pm b/perl/Sesse/pr0n/Select.pm
new file mode 100644 (file)
index 0000000..1086587
--- /dev/null
@@ -0,0 +1,47 @@
+package Sesse::pr0n::Select;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Common qw(error dberror);
+use Apache2::Request;
+
+sub handler {
+       my $r = shift;
+       my $apr = Apache2::Request->new($r);
+       my $dbh = Sesse::pr0n::Common::get_dbh();
+       my ($user, $takenby) = Sesse::pr0n::Common::check_access($r);
+       if (!defined($user)) {
+               return Apache2::Const::OK;
+       }
+
+       my $event = $apr->param('event');
+
+       Sesse::pr0n::Common::header($r, "Selection results");
+
+       {
+               # Enable transactions and error raising temporarily
+               local $dbh->{AutoCommit} = 0;
+               local $dbh->{RaiseError} = 1;
+
+               $dbh->do('UPDATE images SET selected=\'f\' WHERE event=?', undef, $event);
+
+               my @params = $apr->param();
+               my $key;
+               for $key (@params) {
+                       if ($key =~ /^sel-(\d+)/ && $apr->param($key) eq 'on') {
+                               my $id = $1;
+                               my $q = $dbh->do('UPDATE images SET selected=\'t\' WHERE id=?', undef, $id)
+                                       or dberror($r, "Selection of $id failed: $!");
+                               $r->print("    <p>Selected image ID `$id'.</p>\n");
+                       }
+               }
+       }
+
+       Sesse::pr0n::Common::footer($r);
+
+       return Apache2::Const::OK;
+}
+
+1;
+
+
diff --git a/perl/Sesse/pr0n/Single.pm b/perl/Sesse/pr0n/Single.pm
new file mode 100644 (file)
index 0000000..4971df7
--- /dev/null
@@ -0,0 +1,64 @@
+package Sesse::pr0n::Single;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Common;
+use Sesse::pr0n::Index;
+use Apache2::Request;
+use POSIX;
+
+sub handler {
+       my $r = shift;
+       my $apr = Apache2::Request->new($r);
+
+       # Read the appropriate settings from the query string into the settings hash
+        my %defsettings = (
+                thumbxres => 80,
+                thumbyres => 64,
+                xres => undef,
+                yres => undef,
+                start => 1,
+                num => undef,
+                svurr => 0
+        );
+        my %settings = %defsettings;
+
+       for my $s qw(thumbxres thumbyres xres yres svurr start num) {
+               my $val = $apr->param($s);
+               if (defined($val) && $val =~ /^(\d+)$/) {
+                       $settings{$s} = $val;
+               }
+       }
+
+       my $thumbxres = $settings{'thumbxres'};
+       my $thumbyres = $settings{'thumbyres'};
+       my $xres = $settings{'xres'};
+       my $yres = $settings{'yres'};
+       my $start = $settings{'start'};
+       my $num = $settings{'num'};
+
+       # Print the page itself
+       Sesse::pr0n::Common::header($r, "Singles");
+
+       Sesse::pr0n::Index::print_thumbsize($r, 'single', \%settings, \%defsettings);
+       Sesse::pr0n::Index::print_viewres($r, 'single', \%settings, \%defsettings);
+
+       for my $id ($start..($start+$num)) { 
+               my $filename = "$id.jpeg";
+               my $uri = $filename;
+               if (defined($xres) && defined($yres)) {
+                       $uri = "${xres}x$yres/$filename";
+               }
+               
+               $r->print("      <a href=\"$uri\"><img src=\"${thumbxres}x${thumbyres}/$filename\" alt=\"\" /></a>\n");
+       }
+       $r->print("    </p>\n");
+
+       Sesse::pr0n::Common::footer($r);
+
+       return Apache2::Const::OK;
+}
+
+1;
+
+
diff --git a/perl/Sesse/pr0n/Templates.pm b/perl/Sesse/pr0n/Templates.pm
new file mode 100644 (file)
index 0000000..4227486
--- /dev/null
@@ -0,0 +1,87 @@
+package Sesse::pr0n::Templates;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Common qw(error dberror);
+
+BEGIN {
+       use Exporter ();
+       our ($VERSION, @ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS);
+
+       $VERSION     = 1.00;
+       @ISA         = qw(Exporter);
+       @EXPORT      = qw();
+       %EXPORT_TAGS = qw();
+       @EXPORT_OK   = qw();
+}
+our %dirs;
+
+sub update_dirs {
+       my $r = shift;
+       my $base = $r->dir_config('TemplateBase');
+       %dirs = ();
+       
+       for my $dir (<$base/*>) {
+               next unless -d $dir;
+               $dir =~ m#/([^/]+)$#;
+               
+               warn "Templates exist for '$1'";
+               $dirs{$1} = {};
+       }
+}
+
+sub r_to_dir {
+       my $r = shift;
+
+       if (!defined(%dirs)) {
+               update_dirs($r);
+       }
+       
+       my $site = $r->get_server_name();
+       if (defined($dirs{$site})) {
+               return $site;
+       } else {
+               return "default";
+       }
+}
+
+sub fetch_template {
+       my ($r, $template) = @_;
+
+       my $dir = r_to_dir($r);
+       my $cache = $dirs{$dir}{$template};
+       if (defined($cache) && time - $cache->{'time'} <= 300) {
+               return $cache->{'contents'};
+       }
+
+       my $newcache = {};
+
+       my $base = $r->dir_config('TemplateBase');
+       open TEMPLATE, "<$base/$dir/$template"
+               or Sesse::pr0n::Common::error($r, "Couldn't open $dir/$template: $!");
+
+       local $/;
+       $newcache->{'contents'} = <TEMPLATE>;
+
+       close TEMPLATE;
+
+       $newcache->{'time'} = time;
+       $dirs{$dir}{$template} = $newcache;
+       return $newcache->{'contents'};
+}
+
+sub print_template {
+       my ($r, $template, $args) = @_;
+       my $text = fetch_template($r, $template);
+
+       # do substitutions
+       while (my ($key, $value) = each (%$args)) {
+               $key = "%" . uc($key) . "%";
+               $text =~ s/$key/$value/g;
+       }
+
+       $r->print($text);
+}
+
+1;
+
diff --git a/perl/Sesse/pr0n/WebDAV.pm b/perl/Sesse/pr0n/WebDAV.pm
new file mode 100644 (file)
index 0000000..35cc979
--- /dev/null
@@ -0,0 +1,606 @@
+package Sesse::pr0n::WebDAV;
+use strict;
+use warnings;
+
+use Sesse::pr0n::Common qw(error dberror);
+use Digest::SHA1;
+use MIME::Base64;
+
+sub handler {
+       my $r = shift;
+       my $dbh = Sesse::pr0n::Common::get_dbh();
+
+       $r->headers_out->{'DAV'} = "1,2";
+
+       # We only handle depth=0, depth=1 (cf. the RFC)
+       my $depth = $r->headers_in->{'depth'};
+       $depth = 0 if (!defined($depth));
+       if (defined($depth) && $depth ne "0" && $depth ne "1") {
+               $r->content_type('text/plain; charset="utf-8"');
+               $r->status(403);
+               $r->print("Invalid depth setting");
+               return Apache2::Const::OK;
+       }
+
+       my ($user,$takenby) = Sesse::pr0n::Common::check_access($r);
+       if (!defined($user)) {
+               return Apache2::Const::OK;
+       }
+
+       # Just "ping, are you alive and do you speak WebDAV"
+       if ($r->method eq "OPTIONS") {
+               $r->content_type('text/plain; charset="utf-8"');
+               $r->status(200);
+               $r->headers_out->{'allow'} = 'OPTIONS,PUT';
+               $r->headers_out->{'ms-author-via'} = 'DAV';
+               return Apache2::Const::OK;
+       }
+       
+       # Directory listings et al
+       if ($r->method eq "PROPFIND") {
+               $r->content_type('text/xml; charset="utf-8"');
+               $r->status(207);
+
+               if ($r->uri =~ m#^/webdav/?$#) {
+                       $r->headers_out->{'content-location'} = "/webdav/";
+               
+                       # Root directory
+                       $r->print(<<"EOF");
+<?xml version="1.0" encoding="utf-8"?>
+<multistatus xmlns="DAV:">
+  <response>
+     <href>/webdav/</href>
+     <propstat>
+        <prop>
+         <resourcetype><collection/></resourcetype>
+         <getcontenttype>text/xml</getcontenttype>
+       </prop>
+        <status>HTTP/1.1 200 OK</status>
+     </propstat>
+  </response>
+EOF
+
+                       # Optionally list the upload/ dir
+                       if ($depth >= 1) {
+                               $r->print(<<"EOF");
+  <response>
+     <href>/webdav/upload/</href>
+     <propstat>
+       <prop>
+         <resourcetype><collection/></resourcetype>
+         <getcontenttype>text/xml</getcontenttype>
+       </prop>
+       <status>HTTP/1.1 200 OK</status>
+     </propstat>
+  </response>
+EOF
+                       }
+                       $r->print("</multistatus>\n");
+                } elsif ($r->uri =~ m#^/webdav/upload/?$#) {
+                       $r->headers_out->{'content-location'} = "/webdav/upload/";
+                       
+                       # Upload root directory
+                       $r->print(<<"EOF");
+<?xml version="1.0" encoding="utf-8"?>
+<multistatus xmlns="DAV:">
+  <response>
+     <href>/webdav/upload/</href>
+     <propstat>
+        <prop>
+         <resourcetype><collection/></resourcetype>
+         <getcontenttype>text/xml</getcontenttype>
+       </prop>
+        <status>HTTP/1.1 200 OK</status>
+     </propstat>
+  </response>
+EOF
+
+                       # Optionally list all events
+                       if ($depth >= 1) {
+                               my $q = $dbh->prepare('SELECT * FROM events WHERE vhost=?') or
+                                       dberror($r, "Couldn't list events");
+                               $q->execute($r->get_server_name) or
+                                       dberror($r, "Couldn't get events");
+               
+                               while (my $ref = $q->fetchrow_hashref()) {
+                                       my $id = $ref->{'id'};
+                                       my $name = $ref->{'name'};
+                               
+                                       $name =~ s/&/\&amp;/g;  # hack :-)
+                                       $r->print(<<"EOF");
+  <response>
+     <href>/webdav/upload/$id/</href>
+     <propstat>
+       <prop>
+         <resourcetype><collection/></resourcetype>
+         <getcontenttype>text/xml</getcontenttype>
+         <displayname>$name</displayname> 
+       </prop>
+       <status>HTTP/1.1 200 OK</status>
+     </propstat>
+  </response>
+EOF
+                               }
+                               $q->finish;
+                       }
+
+                       $r->print("</multistatus>\n");
+               } elsif ($r->uri =~ m#^/webdav/upload/([a-zA-Z0-9-]+)/?$#) {
+                       my $event = $1;
+                       
+                       $r->headers_out->{'content-location'} = "/webdav/upload/$event/";
+                       
+                       # Check that we do indeed exist
+                       my $ref = $dbh->selectrow_hashref('SELECT count(*) AS numev FROM events WHERE id=?',
+                               undef, $event);
+                       if ($ref->{'numev'} != 1) {
+                               $r->status(404);
+                               $r->content_type('text/plain; charset=utf-8');
+                               $r->print("Couldn't find event in database");
+                               return Apache2::Const::OK;
+                       }
+                       
+                       # OK, list the directory
+                       $r->print(<<"EOF");
+<?xml version="1.0" encoding="utf-8"?>
+<multistatus xmlns="DAV:">
+  <response>
+     <href>/webdav/upload/$event/</href>
+     <propstat>
+        <prop>
+         <resourcetype><collection/></resourcetype>
+         <getcontenttype>text/xml</getcontenttype>
+       </prop>
+        <status>HTTP/1.1 200 OK</status>
+     </propstat>
+  </response>
+EOF
+
+                       # List all the files within too, of course :-)
+                       if ($depth >= 1) {
+                               my $q = $dbh->prepare('SELECT * FROM images WHERE event=?') or
+                                       dberror($r, "Couldn't list images");
+                               $q->execute($event) or
+                                       dberror($r, "Couldn't get events");
+               
+                               while (my $ref = $q->fetchrow_hashref()) {
+                                       my $id = $ref->{'id'};
+                                       my $filename = $ref->{'filename'};
+                                       my $fname = Sesse::pr0n::Common::get_disk_location($r, $id);
+                                       my (undef, undef, undef, undef, undef, undef, undef, $size, undef, $mtime) = stat($fname)
+                                               or next;
+                                       $mtime = POSIX::strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime($mtime));
+                                       my $mime_type = Sesse::pr0n::Common::get_mimetype_from_filename($filename);
+
+                                       $r->print(<<"EOF");
+  <response>
+     <href>/webdav/upload/$event/$filename</href>
+     <propstat>
+       <prop>
+         <resourcetype/>
+         <getcontenttype>$mime_type</getcontenttype>
+         <getcontentlength>$size</getcontentlength>
+         <getlastmodified>$mtime</getlastmodified>
+       </prop>
+       <status>HTTP/1.1 200 OK</status>
+     </propstat>
+  </response>
+EOF
+                               }
+                               $q->finish;
+
+                               # And the magical autorename folder
+                               $r->print(<<"EOF");
+  <response>
+     <href>/webdav/upload/$event/autorename/</href>
+     <propstat>
+       <prop>
+         <resourcetype><collection/></resourcetype>
+         <getcontenttype>text/xml</getcontenttype>
+       </prop>
+       <status>HTTP/1.1 200 OK</status>
+     </propstat>
+  </response>
+EOF
+                               $r->log->info("Full list");
+                       }
+
+                       $r->print("</multistatus>\n");
+
+                       return Apache2::Const::OK;
+               } elsif ($r->uri =~ m#^/webdav/upload/([a-zA-Z0-9-]+)/autorename/?$#) {
+                       # The autorename folder is always empty
+                       my $event = $1;
+                       
+                       $r->headers_out->{'content-location'} = "/webdav/upload/$event/autorename/";
+                       
+                       # Check that we do indeed exist
+                       my $ref = $dbh->selectrow_hashref('SELECT count(*) AS numev FROM events WHERE id=?',
+                               undef, $event);
+                       if ($ref->{'numev'} != 1) {
+                               $r->status(404);
+                               $r->content_type('text/plain; charset=utf-8');
+                               $r->print("Couldn't find event in database");
+                               return Apache2::Const::OK;
+                       }
+                       
+                       # OK, list the (empty) directory
+                       $r->print(<<"EOF");
+<?xml version="1.0" encoding="utf-8"?>
+<multistatus xmlns="DAV:">
+  <response>
+     <href>/webdav/upload/$event/autorename/</href>
+     <propstat>
+        <prop>
+         <resourcetype><collection/></resourcetype>
+         <getcontenttype>text/xml</getcontenttype>
+       </prop>
+        <status>HTTP/1.1 200 OK</status>
+     </propstat>
+  </response>
+</multistatus>
+EOF
+       
+                       return Apache2::Const::OK;
+               } elsif ($r->uri =~ m#^/webdav/upload/([a-zA-Z0-9-]+)/([a-zA-Z0-9._-]+)$#) {
+                       # stat a single file
+                       my ($event, $filename) = ($1, $2);
+                       my ($fname, $size, $mtime);
+                       
+                       # check if we have a pending fake file for this
+                       my $ref = $dbh->selectrow_hashref('SELECT count(*) AS numfiles FROM fake_files WHERE event=? AND filename=? AND expires_at > now()',
+                               undef, $event, $filename);
+                       if ($ref->{'numfiles'} == 1) {
+                               $fname = "/dev/null";
+                               $size = 0;
+                               $mtime = time;
+                       } else {
+                               ($fname, $size, $mtime) = Sesse::pr0n::Common::stat_image($r, $event, $filename);
+                       }
+                       
+                       if (!defined($fname)) {
+                               $r->status(404);
+                               $r->content_type('text/plain; charset=utf-8');
+                               $r->print("Couldn't find file");
+                               return Apache2::Const::OK;
+                       }
+                       my $mime_type = Sesse::pr0n::Common::get_mimetype_from_filename($filename);
+                       
+                       $mtime = POSIX::strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime($mtime));
+                       $r->print(<<"EOF");
+<?xml version="1.0" encoding="utf-8"?>
+<multistatus xmlns="DAV:">
+  <response>
+    <href>/webdav/upload/$event/$filename</href>
+    <propstat>
+      <prop>
+        <resourcetype/>
+        <getcontenttype>$mime_type</getcontenttype>
+        <getcontentlength>$size</getcontentlength>
+        <getlastmodified>$mtime</getlastmodified>
+      </prop>
+      <status>HTTP/1.1 200 OK</status>
+    </propstat>
+  </response>
+</multistatus>
+EOF
+                       return Apache2::Const::OK;
+               } elsif ($r->uri =~ m#^/webdav/upload/([a-zA-Z0-9-]+)/autorename/(.{1,250})$#) {
+                       # stat a single file in autorename
+                       my ($event, $filename) = ($1, $2);
+                       my ($fname, $size, $mtime);
+                       
+                       # check if we have a pending fake file for this
+                       my $ref = $dbh->selectrow_hashref('SELECT count(*) AS numfiles FROM fake_files WHERE event=? AND filename=? AND expires_at > now()',
+                               undef, $event, $filename);
+                       if ($ref->{'numfiles'} == 1) {
+                               $fname = "/dev/null";
+                               $size = 0;
+                               $mtime = time;
+                       } else {
+                               # check if we have a "shadow file" for this
+                               my $ref = $dbh->selectrow_hashref('SELECT id FROM shadow_files WHERE event=? AND filename=? AND expires_at > now()',
+                                       undef, $event, $filename);
+                               if (defined($ref)) {
+                                       ($fname, $size, $mtime) = Sesse::pr0n::Common::stat_image_from_id($r, $ref->{'id'});
+                               }
+                       }
+                       
+                       if (!defined($fname)) {
+                               $r->status(404);
+                               $r->content_type('text/plain; charset=utf-8');
+                               $r->print("Couldn't find file");
+                               return Apache2::Const::OK;
+                       }
+                       my $mime_type = Sesse::pr0n::Common::get_mimetype_from_filename($filename);
+                       
+                       $mtime = POSIX::strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime($mtime));
+                       $r->print(<<"EOF");
+<?xml version="1.0" encoding="utf-8"?>
+<multistatus xmlns="DAV:">
+  <response>
+    <href>/webdav/upload/$event/autorename/$filename</href>
+    <propstat>
+      <prop>
+        <resourcetype/>
+        <getcontenttype>$mime_type</getcontenttype>
+        <getcontentlength>$size</getcontentlength>
+        <getlastmodified>$mtime</getlastmodified>
+      </prop>
+      <status>HTTP/1.1 200 OK</status>
+    </propstat>
+  </response>
+</multistatus>
+EOF
+               } else {
+                       $r->status(404);
+                       $r->content_type('text/plain; charset=utf-8');
+                       $r->print("Couldn't find file");
+               }
+               return Apache2::Const::OK;
+       }
+       
+       if ($r->method eq "HEAD" or $r->method eq "GET") {
+               if ($r->uri !~ m#^/webdav/upload/([a-zA-Z0-9-]+)/(autorename/)?(.{1,250})$#) {
+                       $r->status(404);
+                       $r->content_type('text/xml; charset=utf-8');
+                       $r->print("<?xml version=\"1.0\"?>\n<p>Couldn't find file</p>");
+                       return Apache2::Const::OK;
+               }
+
+               my ($event, $autorename, $filename) = ($1, $2, $3);
+               
+               # Check if this file really exists
+               my ($fname, $size, $mtime);
+
+               # check if we have a pending fake file for this
+               my $ref = $dbh->selectrow_hashref('SELECT count(*) AS numfiles FROM fake_files WHERE event=? AND filename=? AND expires_at > now()',
+                       undef, $event, $filename);
+               if ($ref->{'numfiles'} == 1) {
+                       $fname = "/dev/null";
+                       $size = 0;
+                       $mtime = time;
+               } else {
+                       # check if we have a "shadow file" for this
+                       if (defined($autorename) && $autorename eq "autorename/") {
+                               my $ref = $dbh->selectrow_hashref('SELECT id FROM shadow_files WHERE event=? AND filename=? AND expires_at > now()',
+                                       undef, $event, $filename);
+                               if (defined($ref)) {
+                                       ($fname, $size, $mtime) = Sesse::pr0n::Common::stat_image_from_id($r, $ref->{'id'});
+                               }
+                       } elsif (!defined($fname)) {
+                               ($fname, $size, $mtime) = Sesse::pr0n::Common::stat_image($r, $event, $filename);
+                       }
+               }
+               
+               if (!defined($fname)) {
+                       $r->status(404);
+                       $r->content_type('text/plain; charset=utf-8');
+                       $r->print("Couldn't find file");
+                       return Apache2::Const::OK;
+               }
+               
+               $r->status(200);
+               $r->set_content_length($size);
+               $r->set_last_modified($mtime);
+       
+               if ($r->method eq "GET") {
+                       $r->sendfile($fname);
+               }
+               return Apache2::Const::OK;
+       }
+       
+       if ($r->method eq "PUT") {
+               if ($r->uri !~ m#^/webdav/upload/([a-zA-Z0-9-]+)/(autorename/)?(.{1,250})$#) {
+                       $r->status(403);
+                       $r->content_type('text/plain; charset=utf-8');
+                       $r->print("No access");
+                       return Apache2::Const::OK;
+               }
+               
+               my ($event, $autorename, $filename) = ($1, $2, $3);
+               my $size = $r->headers_in->{'content-length'};
+               my $orig_filename = $filename;
+
+               # Remove evil characters
+               if ($filename =~ /[^a-zA-Z0-9._-]/) {
+                       if (defined($autorename) && $autorename eq "autorename/") {
+                               $filename =~ tr/a-zA-Z0-9.-/_/c;
+                       } else {
+                               $r->status(403);
+                               $r->content_type('text/plain; charset=utf-8');
+                               $r->print("Illegal characters in filename");
+                               return Apache2::Const::OK;
+                       }
+               }
+               
+               #
+               # gnome-vfs and mac os x love to make zero-byte files,
+               # make them happy
+               # 
+               if ($r->headers_in->{'content-length'} == 0) {
+                       $dbh->do('DELETE FROM fake_files WHERE expires_at <= now() OR (event=? AND filename=?);',
+                               undef, $event, $filename)
+                               or dberror($r, "Couldn't prune fake_files");
+                       $dbh->do('INSERT INTO fake_files (event,filename,expires_at) VALUES (?,?,now() + interval \'30 seconds\');',
+                               undef, $event, $filename)
+                               or dberror($r, "Couldn't add file");
+                       $r->content_type('text/plain; charset="utf-8"');
+                       $r->status(201);
+                       $r->print("OK");
+                       $r->log->info("Fake upload of $event/$filename");
+                       return Apache2::Const::OK;
+               }
+
+               # Get the new ID
+               my $ref = $dbh->selectrow_hashref("SELECT NEXTVAL('imageid_seq') AS id;");
+               my $newid = $ref->{'id'};
+               if (!defined($newid)) {
+                       dberror($r, "Couldn't get new ID");
+               }
+               
+               # Autorename if we need to
+               if (defined($autorename) && $autorename eq "autorename/") {
+                       my $ref = $dbh->selectrow_hashref("SELECT COUNT(*) AS numfiles FROM images WHERE event=? AND filename=?",
+                               undef, $event, $filename)
+                               or dberror($r, "Couldn't check for existing files");
+                       if ($ref->{'numfiles'} > 0) {
+                               $r->log->info("Renaming $filename to $newid.jpeg");
+                               $filename = "$newid.jpeg";
+                       }
+               }
+               
+               {
+                       # Enable transactions and error raising temporarily
+                       local $dbh->{AutoCommit} = 0;
+                       
+                       local $dbh->{RaiseError} = 1;
+
+                       # Try to insert this new file
+                       eval {
+                               $dbh->do('DELETE FROM fake_files WHERE event=? AND filename=?;',
+                                       undef, $event, $filename);
+                                       
+                               $dbh->do('INSERT INTO images (id,event,uploadedby,takenby,filename) VALUES (?,?,?,?,?);',
+                                       undef, $newid, $event, $user, $takenby, $filename);
+
+                               # Now save the file to disk
+                               my $fname = Sesse::pr0n::Common::get_disk_location($r, $newid);
+                               open NEWFILE, ">$fname"
+                                       or die "$fname: $!";
+
+                               my $buf;
+                               my $content_length = $r->headers_in->{'content-length'};
+                               if ($r->read($buf, $content_length)) {
+                                       print NEWFILE $buf or die "write($fname): $!";
+                               }
+
+                               close NEWFILE or die "close($fname): $!";
+                               
+                               # Orient stuff correctly
+                               system("/usr/bin/exifautotran", $fname) == 0
+                                       or die "/usr/bin/exifautotran: $!";
+
+                               # Make cache while we're at it.
+                               # Don't do it for the resource forks Mac OS X loves to upload :-(
+                               if ($filename !~ /^\._/) {
+                                       Sesse::pr0n::Common::ensure_cached($r, $filename, $newid, -1, -1, 1, 80, 64, 320, 256);
+                               }
+                               
+                               # OK, we got this far, commit
+                               $dbh->commit;
+
+                               $r->log->notice("Successfully wrote $event/$filename to $fname");
+                       };
+                       if ($@) {
+                               # Some error occurred, rollback and bomb out
+                               $dbh->rollback;
+                               dberror($r, "Transaction aborted because $@");
+                       }
+               }
+
+               # Insert a `shadow file' we can stat the next 30 secs
+               if (defined($autorename) && $autorename eq "autorename/") {
+                       $dbh->do('DELETE FROM shadow_files WHERE expires_at <= now() OR (event=? AND filename=?);',
+                               undef, $event, $filename)
+                               or dberror($r, "Couldn't prune shadow_files");
+                       $dbh->do('INSERT INTO shadow_files (event,filename,id,expires_at) VALUES (?,?,?,now() + interval \'30 seconds\');',
+                               undef, $event, $orig_filename, $newid)
+                               or dberror($r, "Couldn't add shadow file");
+                       $r->log->info("Added shadow entry for $event/$filename");
+               }
+
+               $r->content_type('text/plain; charset="utf-8"');
+               $r->status(201);
+               $r->print("OK");
+
+               return Apache2::Const::OK;
+       }
+
+       # Yes, we fake locks. :-)
+       if ($r->method eq "LOCK") {
+               if ($r->uri !~ m#^/webdav/upload/([a-zA-Z0-9-]+)/(autorename/)?([a-zA-Z0-9._-]+)$#) {
+                       $r->status(403);
+                       $r->content_type('text/plain; charset=utf-8');
+                       $r->print("No access");
+                       return Apache2::Const::OK;
+               }
+
+               my ($event, $autorename, $filename) = ($1, $2, $3);
+               my $sha1 = Digest::SHA1::sha1_base64("/$event/$autorename/$filename");
+
+               $r->status(200);
+               $r->content_type('text/xml; charset=utf-8');
+
+               $r->print(<<"EOF");
+<?xml version="1.0" encoding="utf-8"?>
+<prop xmlns="DAV:">
+  <lockdiscovery>
+    <activelock>
+      <locktype><write/></locktype>
+      <lockscope><exclusive/></lockscope>
+      <depth>0</depth>
+      <owner>
+        <href>/webdav/upload/$event/$autorename$filename</href>
+      </owner>
+      <timeout>Second-3600</timeout>
+      <locktoken>
+        <href>opaquelocktoken:$sha1</href>
+      </locktoken>
+    </activelock>
+  </lockdiscovery>
+</prop>
+EOF
+               return Apache2::Const::OK;
+       }
+       
+       if ($r->method eq "UNLOCK") {
+               $r->content_type('text/plain; charset="utf-8"');
+               $r->status(200);
+               $r->print("OK");
+
+               return Apache2::Const::OK;
+       }
+
+       if ($r->method eq "DELETE") {
+               if ($r->uri !~ m#^/webdav/upload/([a-zA-Z0-9-]+)/(autorename/)?(\._[a-zA-Z0-9._-]+)$#) {
+                       $r->status(403);
+                       $r->content_type('text/plain; charset=utf-8');
+                       $r->print("No access");
+                       return Apache2::Const::OK;
+               }
+               
+               my ($event, $autorename, $filename) = ($1, $2, $3);
+               $dbh->do('DELETE FROM images WHERE event=? AND filename=?;',
+                       undef, $event, $filename)
+                       or dberror($r, "Couldn't remove file");
+               $r->status(200);
+               $r->print("OK");
+
+               $r->log->info("deleted $event/$filename");
+               
+               return Apache2::Const::OK;
+       }
+       
+       if ($r->method eq "MOVE" or
+           $r->method eq "MKCOL" or
+           $r->method eq "RMCOL" or
+           $r->method eq "RENAME" or
+           $r->method eq "COPY") {
+               $r->content_type('text/plain; charset="utf-8"');
+               $r->status(403);
+               $r->print("Sorry, you do not have access to that feature.");
+               return Apache2::Const::OK;
+       }
+       
+       $r->content_type('text/plain; charset=utf-8');
+       $r->log->error("unknown method " . $r->method);
+       $r->status(500);
+       $r->print("Unknown method");
+       
+       return Apache2::Const::OK;
+}
+
+1;
+
+
diff --git a/perl/Sesse/pr0n/pr0n.pm b/perl/Sesse/pr0n/pr0n.pm
new file mode 100644 (file)
index 0000000..620bf19
--- /dev/null
@@ -0,0 +1,63 @@
+use Sesse::pr0n::Common;
+use Sesse::pr0n::Listing;
+use Sesse::pr0n::Index;
+use Sesse::pr0n::Image;
+use Sesse::pr0n::Single;
+use Sesse::pr0n::Rotate;
+use Sesse::pr0n::Select;
+use Sesse::pr0n::WebDAV;
+use Sesse::pr0n::NewEvent;
+
+package Sesse::pr0n::pr0n;
+use strict;    
+use warnings;
+
+sub handler {
+       my $r = shift;
+
+       my $uri = $r->uri;
+       if ($uri eq '/') {
+               return Sesse::pr0n::Listing::handler($r);
+       } elsif ($uri eq '/robots.txt' ||
+                $uri eq '/pr0n.css' ||
+                $uri eq '/skoyen.css' ||
+                $uri eq '/blah.png' ||
+                $uri eq '/faq.html' ||
+                $uri =~ m#^/usage/([a-zA-Z0-9_.]+)$#) {
+               $uri =~ s#^/##;
+               $r->content_type(Sesse::pr0n::Common::get_mimetype_from_filename($uri));
+               $r->sendfile(Sesse::pr0n::Common::get_base($r) . $uri);
+               return Apache2::Const::OK;
+       } elsif ($uri eq '/newevent.html') {
+               $r->content_type('text/html; charset=utf-8');
+               $r->sendfile(Sesse::pr0n::Common::get_base($r) . "newevent.html");
+               return Apache2::Const::OK;
+       } elsif ($uri =~ m#^/webdav#) {
+               return Sesse::pr0n::WebDAV::handler($r);
+       } elsif ($uri =~ m#^/usage/([a-zA-Z0-9.-]+)$#) {
+               $r->sendfile(Sesse::pr0n::Common::get_base($r) . "usage/$1");
+               return Apache2::Const::OK;
+       } elsif ($uri =~ m#^/single/?$#) {
+               return Sesse::pr0n::Single::handler($r);
+       } elsif ($uri =~ m#^/rotate$#) {
+               return Sesse::pr0n::Rotate::handler($r);
+       } elsif ($uri =~ m#^/select$#) {
+               return Sesse::pr0n::Select::handler($r);
+       } elsif ($uri =~ m#^/newevent$#) {
+               return Sesse::pr0n::NewEvent::handler($r);
+       } elsif ($uri =~ m#^/[a-zA-Z0-9-]+/?$#) {
+               return Sesse::pr0n::Index::handler($r);
+       } elsif ($uri =~ m#^/[a-zA-Z0-9-]+/(\d+x\d+/)?(nobox/)?[a-zA-Z0-9._-]+$#) {
+               return Sesse::pr0n::Image::handler($r);
+       }
+
+       $r->status(404);
+       Sesse::pr0n::Common::header($r, "404 File Not Found");
+       $r->print("     <p>The file you requested was not found.</p>");
+       Sesse::pr0n::Common::footer($r);
+       return Apache2::Const::OK;
+}
+
+1;
+
+
diff --git a/pr0n.css b/pr0n.css
new file mode 100644 (file)
index 0000000..4d4c6ba
--- /dev/null
+++ b/pr0n.css
@@ -0,0 +1,19 @@
+body {
+       background-color: white;
+       color: black;
+       font-family: verdana, arial, helvetica, sans-serif;
+       font-size: smaller;
+}
+a {
+       text-decoration: none;
+}
+.footer {
+       font-size: smaller;
+       margin-top: 0;
+}
+h2.date {
+       font-size: 1em;
+}
+img {
+       border: 1px solid black;
+}
diff --git a/robots.txt b/robots.txt
new file mode 100644 (file)
index 0000000..c6742d8
--- /dev/null
@@ -0,0 +1,2 @@
+User-Agent: *
+Disallow: /
diff --git a/skoyen.css b/skoyen.css
new file mode 100644 (file)
index 0000000..ce70bd6
--- /dev/null
@@ -0,0 +1,50 @@
+td, body, p, div {
+       font-family: verdana,helvetica; 
+       font-size: 10px;
+}
+
+/* The content of the posts (body of text) */
+body {
+       background: #fcfcfc;
+       color: #000000;
+       font: 12px verdana,arial,helvetica,sans-serif;
+       scrollbar-3dlight-color: #d1d7dc;
+       scrollbar-arrow-color: #006699;
+       scrollbar-darkshadow-color: #98aab1;
+       scrollbar-face-color: #dee3e7;
+       scrollbar-highlight-color: #ffffff;
+       scrollbar-shadow-color: #dee3e7;
+       scrollbar-track-color: #efefef;
+}
+
+/* General page style */
+a:link,a:active,a:visited,a.postlink {
+       color: #006699;
+       text-decoration: underline;
+}
+a:hover {
+       color: #dd6900;
+       text-decoration: none;
+}
+
+/* This is the border line & background colour round the entire page */
+.bodyline {
+       background: #ffffff;
+       border: 1px solid #98aab1;
+}
+
+/* Form elements */
+select, input, textarea {
+       border-color: #006699;
+       color: #000000;
+       font: normal 10px Verdana,Arial,Helvetica,sans-serif;
+}
+input {
+       background-color: #FCFCFC /* url(images/navbar.jpg) */;
+       border-width: 1px;
+}
+textarea {
+       background-color: #FCFCFC;
+       border-width: 1px;
+}
+
diff --git a/templates/bilder.knatten.com/date b/templates/bilder.knatten.com/date
new file mode 100644 (file)
index 0000000..1de41ba
--- /dev/null
@@ -0,0 +1,3 @@
+    <h2 class="date">Dato: %DATE%</h2>
+
+
diff --git a/templates/bilder.knatten.com/event-listing b/templates/bilder.knatten.com/event-listing
new file mode 100644 (file)
index 0000000..7ab4369
--- /dev/null
@@ -0,0 +1 @@
+Bildegalleri
diff --git a/templates/bilder.knatten.com/footer b/templates/bilder.knatten.com/footer
new file mode 100644 (file)
index 0000000..533b320
--- /dev/null
@@ -0,0 +1,8 @@
+    <hr />
+    <p class="footer">pr0n %VERSION%,
+      &copy; 2004-2006 <a href="http://www.sesse.net/">Steinar H. Gunderson</a>.
+      Alle bilder er opphavsrettslig beskyttet og tilhører personen som tok det.</p>
+    </div>
+  </body>
+</html>
+
diff --git a/templates/bilder.knatten.com/header b/templates/bilder.knatten.com/header
new file mode 100644 (file)
index 0000000..a269c50
--- /dev/null
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE
+  html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="no">
+  <head>
+    <title>svurr :: galleri :: %TITLE%</title>
+    <link rel="stylesheet" type="text/css" href="http://www.svurr.com/svurr.css" />
+  </head>
+  <body>
+    <div class="navbar">
+      <a href="http://www.svurr.com/index.html">forsiden</a> ::
+      <a href="http://www.svurr.com/hvaskjer.html">hva skjer</a> ::
+      <a href="http://www.svurr.com/kalender.html">kalender</a> ::
+      <a href="http://www.svurr.com/nyheter.html">nyheter</a> ::
+      galleri ::
+      <a href="http://www.svurr.com/multimedia.html">multimedia</a>
+   </div>
+   <div class="main">
+     <h1>%TITLE%</h1>
+
diff --git a/templates/bilder.knatten.com/imgsperpage b/templates/bilder.knatten.com/imgsperpage
new file mode 100644 (file)
index 0000000..39b55ff
--- /dev/null
@@ -0,0 +1 @@
+Antall bilder per side
diff --git a/templates/bilder.knatten.com/imgsperpage-unlimited b/templates/bilder.knatten.com/imgsperpage-unlimited
new file mode 100644 (file)
index 0000000..d6b2e92
--- /dev/null
@@ -0,0 +1 @@
+Ubegrenset
diff --git a/templates/bilder.knatten.com/infobox b/templates/bilder.knatten.com/infobox
new file mode 100644 (file)
index 0000000..a1dccd7
--- /dev/null
@@ -0,0 +1 @@
+Informasjonsboks
diff --git a/templates/bilder.knatten.com/infobox-off b/templates/bilder.knatten.com/infobox-off
new file mode 100644 (file)
index 0000000..8987383
--- /dev/null
@@ -0,0 +1 @@
+Av
diff --git a/templates/bilder.knatten.com/infobox-on b/templates/bilder.knatten.com/infobox-on
new file mode 100644 (file)
index 0000000..cac1758
--- /dev/null
@@ -0,0 +1 @@
+På
diff --git a/templates/bilder.knatten.com/nextpage b/templates/bilder.knatten.com/nextpage
new file mode 100644 (file)
index 0000000..771ec7b
--- /dev/null
@@ -0,0 +1 @@
+Neste side
diff --git a/templates/bilder.knatten.com/overloadmode b/templates/bilder.knatten.com/overloadmode
new file mode 100644 (file)
index 0000000..e68cf7c
--- /dev/null
@@ -0,0 +1,4 @@
+    <p><strong>Note:</strong> <em>System is currently in overload mode due to high traffic.
+      No server-side scaling (whether of thumbnails or of full-sized images) will be done, so
+      only those already cached will work. We apologize for the inconvenience; please come back
+      when the load has settled. :-)</em></p>
diff --git a/templates/bilder.knatten.com/prevpage b/templates/bilder.knatten.com/prevpage
new file mode 100644 (file)
index 0000000..71bde9d
--- /dev/null
@@ -0,0 +1 @@
+Forrige side
diff --git a/templates/bilder.knatten.com/show b/templates/bilder.knatten.com/show
new file mode 100644 (file)
index 0000000..1c2ce55
--- /dev/null
@@ -0,0 +1 @@
+Vis
diff --git a/templates/bilder.knatten.com/show-all b/templates/bilder.knatten.com/show-all
new file mode 100644 (file)
index 0000000..d1479fa
--- /dev/null
@@ -0,0 +1 @@
+Alle bilder
diff --git a/templates/bilder.knatten.com/show-selected b/templates/bilder.knatten.com/show-selected
new file mode 100644 (file)
index 0000000..cf82511
--- /dev/null
@@ -0,0 +1 @@
+Kun utvalgte bilder
diff --git a/templates/bilder.knatten.com/submittedby b/templates/bilder.knatten.com/submittedby
new file mode 100644 (file)
index 0000000..6d6c8aa
--- /dev/null
@@ -0,0 +1 @@
+    <h2>Tatt av: %AUTHOR%</h2>
diff --git a/templates/bilder.knatten.com/thispage b/templates/bilder.knatten.com/thispage
new file mode 100644 (file)
index 0000000..e3a9ee9
--- /dev/null
@@ -0,0 +1 @@
+Denne siden
diff --git a/templates/bilder.knatten.com/thumbsize b/templates/bilder.knatten.com/thumbsize
new file mode 100644 (file)
index 0000000..d579ae0
--- /dev/null
@@ -0,0 +1 @@
+Størrelse på småbilder
diff --git a/templates/bilder.knatten.com/viewres b/templates/bilder.knatten.com/viewres
new file mode 100644 (file)
index 0000000..9259f61
--- /dev/null
@@ -0,0 +1 @@
+Skalér ned alle bilder (når man klikker på dem) til
diff --git a/templates/bilder.knatten.com/viewres-unlimited b/templates/bilder.knatten.com/viewres-unlimited
new file mode 100644 (file)
index 0000000..0999f6a
--- /dev/null
@@ -0,0 +1 @@
+Ingen skalering
diff --git a/templates/default/date b/templates/default/date
new file mode 100644 (file)
index 0000000..a6521a8
--- /dev/null
@@ -0,0 +1,3 @@
+    <h2 class="date">Date: %DATE%</h2>
+
+
diff --git a/templates/default/event-listing b/templates/default/event-listing
new file mode 100644 (file)
index 0000000..cb77994
--- /dev/null
@@ -0,0 +1 @@
+Event listing
diff --git a/templates/default/footer b/templates/default/footer
new file mode 100644 (file)
index 0000000..71655f5
--- /dev/null
@@ -0,0 +1,8 @@
+    <hr />
+    <p class="footer">pr0n %VERSION%,
+      &copy; 2004-2006 <a href="http://www.sesse.net/">Steinar H. Gunderson</a>. All images
+      are copyright their respective owners; please see the
+      <a href="/faq.html">FAQ</a> for more information.</p>
+  </body>
+</html>
+
diff --git a/templates/default/header b/templates/default/header
new file mode 100644 (file)
index 0000000..bce5910
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE
+  html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+  <head>
+    <title>%TITLE%</title>
+    <link rel="stylesheet" href="/pr0n.css" type="text/css" />
+  </head>
+
+  <body>
+    <h1>%TITLE%</h1>
+
diff --git a/templates/default/imgsperpage b/templates/default/imgsperpage
new file mode 100644 (file)
index 0000000..68d4825
--- /dev/null
@@ -0,0 +1 @@
+Images per page
diff --git a/templates/default/imgsperpage-unlimited b/templates/default/imgsperpage-unlimited
new file mode 100644 (file)
index 0000000..fd50299
--- /dev/null
@@ -0,0 +1 @@
+Unlimited
diff --git a/templates/default/infobox b/templates/default/infobox
new file mode 100644 (file)
index 0000000..3098022
--- /dev/null
@@ -0,0 +1 @@
+Information box
diff --git a/templates/default/infobox-off b/templates/default/infobox-off
new file mode 100644 (file)
index 0000000..ce76936
--- /dev/null
@@ -0,0 +1 @@
+Off
diff --git a/templates/default/infobox-on b/templates/default/infobox-on
new file mode 100644 (file)
index 0000000..0f5ec10
--- /dev/null
@@ -0,0 +1 @@
+On
diff --git a/templates/default/nextpage b/templates/default/nextpage
new file mode 100644 (file)
index 0000000..5ab7085
--- /dev/null
@@ -0,0 +1 @@
+Next page
diff --git a/templates/default/overloadmode b/templates/default/overloadmode
new file mode 100644 (file)
index 0000000..4f387c5
--- /dev/null
@@ -0,0 +1,6 @@
+    <p><strong>Note:</strong> <em>System has automatically entered overload
+      mode due to high traffic. No server-side scaling (whether of thumbnails
+      or of full-sized images) will be done, so only those already cached will
+      work; in addition, the default amount of images shown per page has been
+      set to 100 instead of unlimited. We apologize for the inconvenience;
+      please come back when the load has settled. :-)</em></p>
diff --git a/templates/default/prevpage b/templates/default/prevpage
new file mode 100644 (file)
index 0000000..305271b
--- /dev/null
@@ -0,0 +1 @@
+Previous page
diff --git a/templates/default/show b/templates/default/show
new file mode 100644 (file)
index 0000000..021507b
--- /dev/null
@@ -0,0 +1 @@
+Show
diff --git a/templates/default/show-all b/templates/default/show-all
new file mode 100644 (file)
index 0000000..48fffa4
--- /dev/null
@@ -0,0 +1 @@
+All images
diff --git a/templates/default/show-selected b/templates/default/show-selected
new file mode 100644 (file)
index 0000000..5042a90
--- /dev/null
@@ -0,0 +1 @@
+Only selected
diff --git a/templates/default/submittedby b/templates/default/submittedby
new file mode 100644 (file)
index 0000000..7460ee7
--- /dev/null
@@ -0,0 +1 @@
+    <h2>Taken by: %AUTHOR%</h2>
diff --git a/templates/default/thispage b/templates/default/thispage
new file mode 100644 (file)
index 0000000..4ab29a3
--- /dev/null
@@ -0,0 +1 @@
+This page
diff --git a/templates/default/thumbsize b/templates/default/thumbsize
new file mode 100644 (file)
index 0000000..8a1427c
--- /dev/null
@@ -0,0 +1 @@
+Maxiumum thumbnail size
diff --git a/templates/default/viewres b/templates/default/viewres
new file mode 100644 (file)
index 0000000..8f183bd
--- /dev/null
@@ -0,0 +1 @@
+Maximum viewing resolution
diff --git a/templates/default/viewres-unlimited b/templates/default/viewres-unlimited
new file mode 100644 (file)
index 0000000..fd50299
--- /dev/null
@@ -0,0 +1 @@
+Unlimited
diff --git a/templates/images.tg05.gathering.org/date b/templates/images.tg05.gathering.org/date
new file mode 100644 (file)
index 0000000..a6521a8
--- /dev/null
@@ -0,0 +1,3 @@
+    <h2 class="date">Date: %DATE%</h2>
+
+
diff --git a/templates/images.tg05.gathering.org/event-listing b/templates/images.tg05.gathering.org/event-listing
new file mode 100644 (file)
index 0000000..cb77994
--- /dev/null
@@ -0,0 +1 @@
+Event listing
diff --git a/templates/images.tg05.gathering.org/footer b/templates/images.tg05.gathering.org/footer
new file mode 100644 (file)
index 0000000..4ca240c
--- /dev/null
@@ -0,0 +1,15 @@
+ </td></tr>
+  <tr height="18"><td colspan="3"><bR><br><div id="bottom">"I do not fear computers. I fear the lack of them." - 
+    <i>Isaac Asimov</i></div></td></tr>
+
+  
+  </table>
+  </td></tr></table>
+  
+</td><td valign="top"><img src="http://www.gathering.org/gfx/right_color.png" /></td></tr></table>
+  
+</div>
+
+
+</body>
+</html>
diff --git a/templates/images.tg05.gathering.org/header b/templates/images.tg05.gathering.org/header
new file mode 100644 (file)
index 0000000..019fc21
--- /dev/null
@@ -0,0 +1,59 @@
+<html>
+<head>
+       <title>The Gathering 2005 - The Gathering 2005</title>
+       <link href="http://www.gathering.org/tg05/de.css" rel="stylesheet" type="text/css">
+</head>
+<body>
+
+<div id="frame">
+
+<table cellpadding="0" cellspacing="0"><tr><td>
+<table cellpadding="0" cellspacing="0" border="0">
+  <tr>
+
+  <td colspan="3"><a href="http://www.gathering.org"><img src="http://www.gathering.org/tg05/gfx/header.png" border="0" /></a></td></tr><td colspan="3"><div id="menu"><table cellpadding="0" cellspacing="0"><tr><td><img src="http://www.gathering.org/tg05/gfx/ll1.png" /></td><td>
+        <a href="http://www.gathering.org">
+    <span class="menuelement">Home</span></a>
+    
+    <a href="http://www.gathering.org/espresso/pages/tg05/game/view_top.html">
+    <span class="menuelement">Game</span></a>
+    
+    <a href="http://www.gathering.org/espresso/pages/tg05/event/view_top.html">
+    <span class="menuelement">Demoscene</span></a>
+
+    
+    <a href="http://www.gathering.org/espresso/pages/tg05/partyinfo/view_top.html">
+    <span class="menuelement">Partyinfo</span></a>
+    
+    <a href="http://www.gathering.org/espresso/pages/tg05/entertainment/view_top.html">
+    <span class="menuelement">Entertainment</span></a>
+    
+    <a href="http://www.gathering.org/espresso/pages/tg05/press/view_top.html">
+    <span class="menuelement">Press</span></a>
+    
+    <a href="http://www.gathering.org/espresso/pages/tg05/contact_info/view_top.html">
+
+    <span class="menuelement">Contact Info</span></a>
+    
+    <a href="http://www.gathering.org/espresso/pages/tg05/sponsors/view_top.html">
+    <span class="menuelement">Sponsors</span></a>
+    
+    <a href="http://www.gathering.org/espresso/pages/tg05/articles/view_top.html">
+    <span class="menuelement">Articles</span></a>
+    
+<!--<a href="http://forums.gathering.org" target="_blank">
+    <span class="menuelement">Forum</span></a>-->
+</div>   
+ </td></tr></table>
+    
+   </td></tr><tr><td colspan="3"><img src="http://www.gathering.org/tg05/gfx/spacer.png" /></td></tr>
+
+  <tr><td width="169" valign="top"><table cellpadding="0" cellspacing="0"><tr><td class="left_back"><img src="http://www.gathering.org/tg05/gfx/logo_end_cut.png" />
+  
+  </td><td></td><td></td>
+ </tr>
+ <tr><td colspan="3">
+
+
+  <div class="path">Images</div>
+  <div class="content"><span class="header">%TITLE%</span><br /> 
diff --git a/templates/images.tg05.gathering.org/imgsperpage b/templates/images.tg05.gathering.org/imgsperpage
new file mode 100644 (file)
index 0000000..68d4825
--- /dev/null
@@ -0,0 +1 @@
+Images per page
diff --git a/templates/images.tg05.gathering.org/imgsperpage-unlimited b/templates/images.tg05.gathering.org/imgsperpage-unlimited
new file mode 100644 (file)
index 0000000..fd50299
--- /dev/null
@@ -0,0 +1 @@
+Unlimited
diff --git a/templates/images.tg05.gathering.org/infobox b/templates/images.tg05.gathering.org/infobox
new file mode 100644 (file)
index 0000000..3098022
--- /dev/null
@@ -0,0 +1 @@
+Information box
diff --git a/templates/images.tg05.gathering.org/infobox-off b/templates/images.tg05.gathering.org/infobox-off
new file mode 100644 (file)
index 0000000..ce76936
--- /dev/null
@@ -0,0 +1 @@
+Off
diff --git a/templates/images.tg05.gathering.org/infobox-on b/templates/images.tg05.gathering.org/infobox-on
new file mode 100644 (file)
index 0000000..0f5ec10
--- /dev/null
@@ -0,0 +1 @@
+On
diff --git a/templates/images.tg05.gathering.org/nextpage b/templates/images.tg05.gathering.org/nextpage
new file mode 100644 (file)
index 0000000..5ab7085
--- /dev/null
@@ -0,0 +1 @@
+Next page
diff --git a/templates/images.tg05.gathering.org/overloadmode b/templates/images.tg05.gathering.org/overloadmode
new file mode 100644 (file)
index 0000000..4f387c5
--- /dev/null
@@ -0,0 +1,6 @@
+    <p><strong>Note:</strong> <em>System has automatically entered overload
+      mode due to high traffic. No server-side scaling (whether of thumbnails
+      or of full-sized images) will be done, so only those already cached will
+      work; in addition, the default amount of images shown per page has been
+      set to 100 instead of unlimited. We apologize for the inconvenience;
+      please come back when the load has settled. :-)</em></p>
diff --git a/templates/images.tg05.gathering.org/prevpage b/templates/images.tg05.gathering.org/prevpage
new file mode 100644 (file)
index 0000000..305271b
--- /dev/null
@@ -0,0 +1 @@
+Previous page
diff --git a/templates/images.tg05.gathering.org/show b/templates/images.tg05.gathering.org/show
new file mode 100644 (file)
index 0000000..021507b
--- /dev/null
@@ -0,0 +1 @@
+Show
diff --git a/templates/images.tg05.gathering.org/show-all b/templates/images.tg05.gathering.org/show-all
new file mode 100644 (file)
index 0000000..48fffa4
--- /dev/null
@@ -0,0 +1 @@
+All images
diff --git a/templates/images.tg05.gathering.org/show-selected b/templates/images.tg05.gathering.org/show-selected
new file mode 100644 (file)
index 0000000..5042a90
--- /dev/null
@@ -0,0 +1 @@
+Only selected
diff --git a/templates/images.tg05.gathering.org/submittedby b/templates/images.tg05.gathering.org/submittedby
new file mode 100644 (file)
index 0000000..7460ee7
--- /dev/null
@@ -0,0 +1 @@
+    <h2>Taken by: %AUTHOR%</h2>
diff --git a/templates/images.tg05.gathering.org/thispage b/templates/images.tg05.gathering.org/thispage
new file mode 100644 (file)
index 0000000..4ab29a3
--- /dev/null
@@ -0,0 +1 @@
+This page
diff --git a/templates/images.tg05.gathering.org/thumbsize b/templates/images.tg05.gathering.org/thumbsize
new file mode 100644 (file)
index 0000000..8a1427c
--- /dev/null
@@ -0,0 +1 @@
+Maxiumum thumbnail size
diff --git a/templates/images.tg05.gathering.org/viewres b/templates/images.tg05.gathering.org/viewres
new file mode 100644 (file)
index 0000000..8f183bd
--- /dev/null
@@ -0,0 +1 @@
+Maximum viewing resolution
diff --git a/templates/images.tg05.gathering.org/viewres-unlimited b/templates/images.tg05.gathering.org/viewres-unlimited
new file mode 100644 (file)
index 0000000..fd50299
--- /dev/null
@@ -0,0 +1 @@
+Unlimited
diff --git a/templates/itk-bilder.samfundet.no/date b/templates/itk-bilder.samfundet.no/date
new file mode 100644 (file)
index 0000000..1de41ba
--- /dev/null
@@ -0,0 +1,3 @@
+    <h2 class="date">Dato: %DATE%</h2>
+
+
diff --git a/templates/itk-bilder.samfundet.no/event-listing b/templates/itk-bilder.samfundet.no/event-listing
new file mode 100644 (file)
index 0000000..cb77994
--- /dev/null
@@ -0,0 +1 @@
+Event listing
diff --git a/templates/itk-bilder.samfundet.no/footer b/templates/itk-bilder.samfundet.no/footer
new file mode 100644 (file)
index 0000000..3507199
--- /dev/null
@@ -0,0 +1,12 @@
+
+<p class="bottom">
+Mail: <a href="mailto:itk@samfundet.no">itk@samfundet.no</a> |
+Telefon: 73 89 95 51 |
+pr0n %VERSION%, &copy; 2004-2006 <a href="http://www.sesse.net/">Steinar H. Gunderson</a>.
+</p>
+
+</div>
+
+</body>
+
+</html>
diff --git a/templates/itk-bilder.samfundet.no/header b/templates/itk-bilder.samfundet.no/header
new file mode 100644 (file)
index 0000000..dad4d00
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+"http://www.w3.org/TR/xhtml11/DTD/xhtml11-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="no">
+
+<head>
+       <title>IT-komiteen</title>
+       <link rel="stylesheet" type="text/css" href="http://itk.samfundet.no/include/style.css" media="screen" />
+       <link rel="stylesheet" type="text/css" href="http://itk.samfundet.no/include/printstyle.css" media="print"/>
+</head>
+
+<body>
+
+<div class="sidebar">
+
+<p class="logo">
+       <a href="/"><img src="http://itk.samfundet.no/images/itkinside.png" width="149" height="134" alt="ITK Inside" /></a><br />
+       IT-komiteen
+</p>
+
+<p class="box">
+       <a href="http://itk.samfundet.no/">Hovedside</a><br />
+       <a href="http://itk.samfundet.no/instruks/">Instruks</a><br />
+
+       <a href="http://itk.samfundet.no/medlemmer/">Medlemmer</a><br />
+       <a href="http://itk.samfundet.no/dok/">Dokumentasjon</a><br />
+       <a href="http://itk.samfundet.no/arkiv/">Arkiv</a><br />
+       <a href="/">Bilder</a><br />
+       <a href="http://itk.samfundet.no/intern/">Interne sider</a><br />
+</p>
+
+<p class="box">
+       <a href="http://itk.samfundet.no/dok-filer/samfundet-ca.crt">SSL-rotsertifikat</a><br />
+
+       <a href="https://lists.samfundet.no/amsit">Mailinglister: Amsit</a><br />
+       <a href="https://lists.samfundet.no/">Mailinglister: Mailman</a><br />
+</p>
+
+<p class="box">
+%QUOTES%
+</p>
+
+<div class="powered">
+       <a href="http://www.debian.org/"><img src="http://itk.samfundet.no/images/debian.png" width="88" height="30" alt="Powered by Debian" /></a>
+       <a href="http://validator.w3.org/check/referer"><img src="http://itk.samfundet.no/images/valid-xhtml11.png" width="88" height="31" alt="Valid XHTML 1.1" /></a>
+</div>
+
+</div>
+
+<div class="content">
+
+<h1>%TITLE%</h1>
+
+
diff --git a/templates/itk-bilder.samfundet.no/imgsperpage b/templates/itk-bilder.samfundet.no/imgsperpage
new file mode 100644 (file)
index 0000000..39b55ff
--- /dev/null
@@ -0,0 +1 @@
+Antall bilder per side
diff --git a/templates/itk-bilder.samfundet.no/imgsperpage-unlimited b/templates/itk-bilder.samfundet.no/imgsperpage-unlimited
new file mode 100644 (file)
index 0000000..d6b2e92
--- /dev/null
@@ -0,0 +1 @@
+Ubegrenset
diff --git a/templates/itk-bilder.samfundet.no/infobox b/templates/itk-bilder.samfundet.no/infobox
new file mode 100644 (file)
index 0000000..a1dccd7
--- /dev/null
@@ -0,0 +1 @@
+Informasjonsboks
diff --git a/templates/itk-bilder.samfundet.no/infobox-off b/templates/itk-bilder.samfundet.no/infobox-off
new file mode 100644 (file)
index 0000000..8987383
--- /dev/null
@@ -0,0 +1 @@
+Av
diff --git a/templates/itk-bilder.samfundet.no/infobox-on b/templates/itk-bilder.samfundet.no/infobox-on
new file mode 100644 (file)
index 0000000..cac1758
--- /dev/null
@@ -0,0 +1 @@
+På
diff --git a/templates/itk-bilder.samfundet.no/nextpage b/templates/itk-bilder.samfundet.no/nextpage
new file mode 100644 (file)
index 0000000..771ec7b
--- /dev/null
@@ -0,0 +1 @@
+Neste side
diff --git a/templates/itk-bilder.samfundet.no/overloadmode b/templates/itk-bilder.samfundet.no/overloadmode
new file mode 100644 (file)
index 0000000..32ca315
--- /dev/null
@@ -0,0 +1,4 @@
+    <p><strong>Note:</strong> <em>System is currently in overload mode due to high traffic.
+      No server-side scaling (whether of thumbnails or of full-sized images) will be done, so
+      only those already cached will work. We apologize for the inconvenience; please come back
+      when the load has settled. :-)</p>
diff --git a/templates/itk-bilder.samfundet.no/prevpage b/templates/itk-bilder.samfundet.no/prevpage
new file mode 100644 (file)
index 0000000..71bde9d
--- /dev/null
@@ -0,0 +1 @@
+Forrige side
diff --git a/templates/itk-bilder.samfundet.no/show b/templates/itk-bilder.samfundet.no/show
new file mode 100644 (file)
index 0000000..1c2ce55
--- /dev/null
@@ -0,0 +1 @@
+Vis
diff --git a/templates/itk-bilder.samfundet.no/show-all b/templates/itk-bilder.samfundet.no/show-all
new file mode 100644 (file)
index 0000000..d1479fa
--- /dev/null
@@ -0,0 +1 @@
+Alle bilder
diff --git a/templates/itk-bilder.samfundet.no/show-selected b/templates/itk-bilder.samfundet.no/show-selected
new file mode 100644 (file)
index 0000000..cf82511
--- /dev/null
@@ -0,0 +1 @@
+Kun utvalgte bilder
diff --git a/templates/itk-bilder.samfundet.no/submittedby b/templates/itk-bilder.samfundet.no/submittedby
new file mode 100644 (file)
index 0000000..6d6c8aa
--- /dev/null
@@ -0,0 +1 @@
+    <h2>Tatt av: %AUTHOR%</h2>
diff --git a/templates/itk-bilder.samfundet.no/thispage b/templates/itk-bilder.samfundet.no/thispage
new file mode 100644 (file)
index 0000000..e3a9ee9
--- /dev/null
@@ -0,0 +1 @@
+Denne siden
diff --git a/templates/itk-bilder.samfundet.no/thumbsize b/templates/itk-bilder.samfundet.no/thumbsize
new file mode 100644 (file)
index 0000000..d579ae0
--- /dev/null
@@ -0,0 +1 @@
+Størrelse på småbilder
diff --git a/templates/itk-bilder.samfundet.no/viewres b/templates/itk-bilder.samfundet.no/viewres
new file mode 100644 (file)
index 0000000..9259f61
--- /dev/null
@@ -0,0 +1 @@
+Skalér ned alle bilder (når man klikker på dem) til
diff --git a/templates/itk-bilder.samfundet.no/viewres-unlimited b/templates/itk-bilder.samfundet.no/viewres-unlimited
new file mode 100644 (file)
index 0000000..0999f6a
--- /dev/null
@@ -0,0 +1 @@
+Ingen skalering
diff --git a/templates/skoyen.bilder.knatten.com/date b/templates/skoyen.bilder.knatten.com/date
new file mode 100644 (file)
index 0000000..1de41ba
--- /dev/null
@@ -0,0 +1,3 @@
+    <h2 class="date">Dato: %DATE%</h2>
+
+
diff --git a/templates/skoyen.bilder.knatten.com/event-listing b/templates/skoyen.bilder.knatten.com/event-listing
new file mode 100644 (file)
index 0000000..7ab4369
--- /dev/null
@@ -0,0 +1 @@
+Bildegalleri
diff --git a/templates/skoyen.bilder.knatten.com/footer b/templates/skoyen.bilder.knatten.com/footer
new file mode 100644 (file)
index 0000000..71655f5
--- /dev/null
@@ -0,0 +1,8 @@
+    <hr />
+    <p class="footer">pr0n %VERSION%,
+      &copy; 2004-2006 <a href="http://www.sesse.net/">Steinar H. Gunderson</a>. All images
+      are copyright their respective owners; please see the
+      <a href="/faq.html">FAQ</a> for more information.</p>
+  </body>
+</html>
+
diff --git a/templates/skoyen.bilder.knatten.com/header b/templates/skoyen.bilder.knatten.com/header
new file mode 100644 (file)
index 0000000..0c3ac83
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE
+  html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+  "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="no">
+  <head>
+    <title>%TITLE%</title>
+    <link rel="stylesheet" href="/skoyen.css" type="text/css" />
+  </head>
+
+  <body>
+    <h1>%TITLE%</h1>
+
diff --git a/templates/skoyen.bilder.knatten.com/imgsperpage b/templates/skoyen.bilder.knatten.com/imgsperpage
new file mode 100644 (file)
index 0000000..39b55ff
--- /dev/null
@@ -0,0 +1 @@
+Antall bilder per side
diff --git a/templates/skoyen.bilder.knatten.com/imgsperpage-unlimited b/templates/skoyen.bilder.knatten.com/imgsperpage-unlimited
new file mode 100644 (file)
index 0000000..d6b2e92
--- /dev/null
@@ -0,0 +1 @@
+Ubegrenset
diff --git a/templates/skoyen.bilder.knatten.com/infobox b/templates/skoyen.bilder.knatten.com/infobox
new file mode 100644 (file)
index 0000000..a1dccd7
--- /dev/null
@@ -0,0 +1 @@
+Informasjonsboks
diff --git a/templates/skoyen.bilder.knatten.com/infobox-off b/templates/skoyen.bilder.knatten.com/infobox-off
new file mode 100644 (file)
index 0000000..8987383
--- /dev/null
@@ -0,0 +1 @@
+Av
diff --git a/templates/skoyen.bilder.knatten.com/infobox-on b/templates/skoyen.bilder.knatten.com/infobox-on
new file mode 100644 (file)
index 0000000..cac1758
--- /dev/null
@@ -0,0 +1 @@
+På
diff --git a/templates/skoyen.bilder.knatten.com/nextpage b/templates/skoyen.bilder.knatten.com/nextpage
new file mode 100644 (file)
index 0000000..771ec7b
--- /dev/null
@@ -0,0 +1 @@
+Neste side
diff --git a/templates/skoyen.bilder.knatten.com/overloadmode b/templates/skoyen.bilder.knatten.com/overloadmode
new file mode 100644 (file)
index 0000000..e68cf7c
--- /dev/null
@@ -0,0 +1,4 @@
+    <p><strong>Note:</strong> <em>System is currently in overload mode due to high traffic.
+      No server-side scaling (whether of thumbnails or of full-sized images) will be done, so
+      only those already cached will work. We apologize for the inconvenience; please come back
+      when the load has settled. :-)</em></p>
diff --git a/templates/skoyen.bilder.knatten.com/prevpage b/templates/skoyen.bilder.knatten.com/prevpage
new file mode 100644 (file)
index 0000000..71bde9d
--- /dev/null
@@ -0,0 +1 @@
+Forrige side
diff --git a/templates/skoyen.bilder.knatten.com/show b/templates/skoyen.bilder.knatten.com/show
new file mode 100644 (file)
index 0000000..1c2ce55
--- /dev/null
@@ -0,0 +1 @@
+Vis
diff --git a/templates/skoyen.bilder.knatten.com/show-all b/templates/skoyen.bilder.knatten.com/show-all
new file mode 100644 (file)
index 0000000..d1479fa
--- /dev/null
@@ -0,0 +1 @@
+Alle bilder
diff --git a/templates/skoyen.bilder.knatten.com/show-selected b/templates/skoyen.bilder.knatten.com/show-selected
new file mode 100644 (file)
index 0000000..cf82511
--- /dev/null
@@ -0,0 +1 @@
+Kun utvalgte bilder
diff --git a/templates/skoyen.bilder.knatten.com/submittedby b/templates/skoyen.bilder.knatten.com/submittedby
new file mode 100644 (file)
index 0000000..6d6c8aa
--- /dev/null
@@ -0,0 +1 @@
+    <h2>Tatt av: %AUTHOR%</h2>
diff --git a/templates/skoyen.bilder.knatten.com/thispage b/templates/skoyen.bilder.knatten.com/thispage
new file mode 100644 (file)
index 0000000..e3a9ee9
--- /dev/null
@@ -0,0 +1 @@
+Denne siden
diff --git a/templates/skoyen.bilder.knatten.com/thumbsize b/templates/skoyen.bilder.knatten.com/thumbsize
new file mode 100644 (file)
index 0000000..d579ae0
--- /dev/null
@@ -0,0 +1 @@
+Størrelse på småbilder
diff --git a/templates/skoyen.bilder.knatten.com/viewres b/templates/skoyen.bilder.knatten.com/viewres
new file mode 100644 (file)
index 0000000..9259f61
--- /dev/null
@@ -0,0 +1 @@
+Skalér ned alle bilder (når man klikker på dem) til
diff --git a/templates/skoyen.bilder.knatten.com/viewres-unlimited b/templates/skoyen.bilder.knatten.com/viewres-unlimited
new file mode 100644 (file)
index 0000000..0999f6a
--- /dev/null
@@ -0,0 +1 @@
+Ingen skalering