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