Source of “photos.php”
<?php // Emacs settings: -*- mode: Fundamental; tab-width: 4; -*-

////////////////////////////////////////////////////////////////////////////
//                                                                        //
// Andrew's Album Application: photos.php                                 //
//                                                                        //
// Copyright (c) 2004-2005, Andrew Birrell                                //
//                                                                        //
// This script delivers XML (op=xml) describing the images stored in its  //
// C_images sub-directory.  It also maintains (op=xmlSave) a title        //
// database for the images.  Finally, it creates, and caches scaled       //
// versions of the images for use as thumbnails and screen-sized          //
// versions of the images.                                                //
//                                                                        //
// This script is intended to be called from a client-side program.       //
//                                                                        //
// This script doesn't generate any HTML itself, only XML.                //
//                                                                        //
// Place this script in the top-level directory of your photograph album, //
// and place the raw photographs in the C_images sub-directory there,     //
// world-readable.                                                        //
//                                                                        //
// You need to create two sub-directories there, named with the values    //
// of the "C_cache" and "C_titles" constants defined below, and you need  //
// to grant RWX access to those two directories to this program running   //
// under your web server.  For example:                                   //
//                                                                        //
//    mkdir cache                                                         //
//    mkdir titles                                                        //
//    chgrp apache cache titles                                           //
//    chmod 775 cache titles                                              //
//                                                                        //
// The only non-obvious dependency is on the "convert" and "identify"     //
// programs (part of the "ImageMagick" package).                          //
//                                                                        //
////////////////////////////////////////////////////////////////////////////


$start = microtime();
require("subroutines.txt");


//
// Acquire parameters
//

$args = ($_SERVER['REQUEST_METHOD'] == "POST" ? $_POST : $_GET);

$op = (isset($args["op"]) ? $args["op"] : "missing");
$pda = isset($args["pda"]); // set is device is a PDA, e.g. iPhone

$argPath = (isset($args["path"]) ? $args["path"] : "");
if (get_magic_quotes_gpc() == 1) $argPath = stripslashes($argPath);
$path = cleanPath($argPath); // Canonicalize and restrict to relative path
$path = C_images . ($path == "." ? "" : "/$path");

if (!file_exists($path)) { // find a path with the same basename instead
    $basename = basename($path);
    $path = findPath($basename);
    if (is_null($path)) $path = C_images;
}

// title, user, time, hmac for "xmlsave"
$title = (isset($args["title"]) ? $args["title"] : "");
if (get_magic_quotes_gpc() == 1) $title = stripslashes($title);
$user = (isset($args["user"]) ? $args["user"] : "");
if (get_magic_quotes_gpc() == 1) $user = stripslashes($user);
$time = (isset($args["time"]) ? 0 + $args["time"] : 0);
$hmac = (isset($args["hmac"]) ? $args["hmac"] : "");

set_magic_quotes_runtime(0);
set_time_limit(0);

//
// The operations
//

header("Expires: Sat, 1 Jan 2000 00:00:01 GMT");
header("Cache-Control: no-store, no-cache, " .
    "must-revalidate, proxy-revalidate");
header("Content-type: text/xml; charset=UTF-8");
ob_start();
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

if ($op == "xml") {
    // Get XML describing the image or folder $path

    // Top-level result is <folder> or <photo> tag.
    //
    // All <folder> and <photo> tags have path and title attributes.
    // Each can have a display image: width, height, and src attributes.
    // <photo> tag additionally has raw attribute, for the raw image's URL,
    // size attribute and optional date and exposure attributes.
    // Top-level <photo> and <folder> tags additionally have
    // next, prev, skip attributes.
    //
    // Top-level <folder> and <photo> tags have <parent> child tags,
    // describing the folder's or photo's ancestry.
    //
    // Top-level <folder> tag has <folder> and <photo> child tags,
    // describing the folder's immediate contents. It also has a
    // first attribute, giving the first image in the folder or its
    // sub-folders.
    //
    // Top-level <photo> tag has no children.
    //
    // <parent> tags have path and title attributes.

    if (is_dir($path)) {
        $next = findNextDir($path, true);
        $prev = findPrevDir($path);
        $skipNext = findNextDir($path, false);
        $firstImage = findFirst($path);
        $entries = getEntries($path);
        ?>
<folder
  path="<?php echo urlPath($path) ?>"
  next="<?php echo urlPath($next) ?>"
  prev="<?php echo urlPath($prev) ?>"
  skip="<?php echo urlPath($skipNext) ?>"
  first="<?php echo urlPath($firstImage) ?>"
  folderMaxW="<?php echo C_miniW+2*C_shadowW ?>"
  folderMaxH="<?php echo C_miniH+2*C_shadowW ?>"
  photoMaxW="<?php echo C_thumbW+2*C_shadowW ?>"
  starLevelNotUsed="0">
<?php
        putTitleXML($path);
        putParentXML($path);
        foreach ($entries->dirs as $entry) {
            $thisPath = "$path/$entry";
            $firstImage = findFirst($thisPath);
            if (!is_null($firstImage)) {
                ?>
  <folder
    path="<?php echo urlPath($thisPath) ?>"
<?php putImageXML($firstImage, C_miniSize, 1) ?>>
<?php putTitleXML($thisPath) ?>
  </folder>
<?php
            }
        }
        foreach ($entries->images as $entry) {
            $thisPath = "$path/$entry";
            ?>
  <photo
    path="<?php echo urlPath($thisPath) ?>"
<?php putImageXML($thisPath, C_thumbSize, 1) ?>
    raw="<?php echo urlForRawImage($thisPath) ?>">
<?php putTitleXML($thisPath) ?>
  </photo>
<?php
        }
        ?>
  <elapsed msec="<?php echo elapsed($start) ?>"/>
</folder>
<?php
    } else {
        $skipNextD = findNextDir(dirname($path), true);
        $skipNext = (is_null($skipNextD) ? null : findFirst($skipNextD));
        $prev = findPrev($path);
        $next = findNext($path);
        cacheScaledImage($path, C_thumbSize); // preload cache
        ?>
<photo
  path="<?php echo urlPath($path) ?>"
  next="<?php echo urlPath($next) ?>"
  prev="<?php echo urlPath($prev) ?>"
  skip="<?php echo urlPath($skipNext) ?>"
  starLevelNotUsed="0"
<?php putCommentsXML($path) ?>
<?php putImageXML($path, ($pda ? C_pdaSize : C_mainSize), 1) ?>
  raw="<?php echo urlForRawImage($path) ?>">
<?php
        putTitleXML($path);
        putParentXML($path) ?>
  <elapsed msec="<?php echo elapsed($start) ?>"/>
</photo>
<?php
    }

} else if ($op == "xmlSave") {
    // Perform a "save" operation for a title
    // Result XML is a <save> tag with path, title and status attributes

    // We authenticate by HMAC-MD5 on the arguments, keyed by user's pwd.
    // There's no freshness, so we allow replays. Easy to fix if we cared.
    require("crypto-php.txt");
    $pwd = getPwd($user);
    $nowSec = time();
    $authenticated = ($user != "" && !is_null($pwd) &&
        $hmac == md5HmacHex("AndrewAlbumApp\x00xmlSave\x00$user\x00" .
                            "$time\x00" . rawurlencode($argPath) .
                            "\x00$title",
                            getPwd($user)));
    if (!$authenticated) {
        $status = "incorrect name or password";
    } else if ($time > $nowSec + 120) {
        $status = "your computer has the wrong time";
    } else if ($time < $nowSec - 300) {
        $status = "request has expired";
    } else {
        writeFileTitle($path, $title);
        $status = "ok";
    }
    ?>
<save
  path="<?php echo urlPath($path) ?>"
  status="<?php echo htmlspecialchars($status) ?>">
  <elapsed msec="<?php echo elapsed($start) ?>"/>
</save>
    <?php


//
// Junk
//

} else {
    // Unknown operation

    // Result XML is an <unknown> tag, with op attribute and text message
    ?>
    <unknownOp
        op="<?php echo htmlspecialchars($op) ?>"
    >Unknown operator "<?php echo htmlspecialchars($op) ?>"</unknownOp>
    <?php
}

ob_end_flush();

?>
End of listing