#!/bin/bash -e
shopt -s nullglob
export PATH=$PATH:/opt/7zip
parseHtaccess() {
header="$(grep " $1" .htaccess | grep AddDescription | cut -d'"' -f2)"
icon="$(grep " $1" .htaccess | grep AddIcon | cut -d' ' -f2)"
title="$(echo "${header}" | sed 's#
.*##' | sed 's|<[^>]*>||g')"
}
generateHeader() {
suffix=""
rootDesc=""
if fileoutput="$(file -bs "$1")"; then
rootDesc="${fileoutput}"
fi
}
fileDetails() {
for (( i = 1; i <= $indentlevel; i++ )); do
echo -n " "
done
echo -n " "
#stat --printf='%A %u %g %10s %-.19y %N' -- "${file}" | sed 's#\&#g; s#<#\<#g;'
echo "
"
}
processFiles() {
echo 'writeElement("name", $file);
$xw->writeElement("mtime", $time);
$xw->writeElement("size", $stats["size"]);
$xw->writeElement("mode", $stats["mode"]);
$xw->writeElement("user", $stats["uid"]);
$xw->writeElement("group", $stats["gid"]);
}
function processFiles($dir) {
global $finfo, $now, $xw;
if ($dh = opendir($dir)) {
while (($file = readdir($dh)) !== false) {
if ($file != "." && $file != "..") {
$isMount = false;
$dirfile = $dir . "/" . $file;
$mode = filetype($dirfile);
$stats = lstat($dirfile);
$time = $stats["mtime"];
// Some archives do not have timestamps stored, e.g. ZIP files often do not contain
// timestamps for directories; the timestamp will then match the extraction date, so
// filter these entries out. However there are also discs with timestamps in the
// future, those should be preserved.
if ($time >= $now && $time <= $now + 1800) {
$time = "";
}
if($mode === "dir") {
$xw->startElement("dir");
storeCommonAttrs($file, $time, $stats);
$xw->writeElement("mime", mime_content_type($dirfile));
$xw->startElement("contents");
processFiles($dirfile);
$xw->endElement();
$xw->endElement();
} elseif($mode === "link") {
$link = readlink($dirfile);
# When extracting files with an absolute path, 7-Zip occasionally adds the current working directory to the root path - remove that.
# Example: links.tgz in SuSELinuxNovember1995_x86_de_CD3.cue
$link = preg_replace("#^/tmp/generateFileListing-[^/]+/extractedFilesOf_[^/]+/#", "/", $link);
$xw->startElement("link");
storeCommonAttrs($file, $time, $stats);
$xw->writeElement("mime", "inode/symlink");
$xw->writeElement("target", $link);
$xw->endElement();
} elseif($mode === "file") {
$retval = 1;
$magic = finfo_file($finfo, $dirfile);
$mime = mime_content_type($dirfile);
do {
$temp_dir = sys_get_temp_dir() . "/extractedFilesOf_" . $file . "_" . mt_rand();
} while (! mkdir($temp_dir));
// 7-Zip´s Linux format support is quite lacking - it does not support
// timestamps for symlinks, special devices such as block or character
// devices, and occasionally adds the current working directory to the
// root path. Use bsdtar / cpio instead.
// Example: links.tgz in SuSELinuxNovember1995_x86_de_CD3.cue
// DEVS.TGZ in SlackWare112BasispaketA_x86_de_Floppy3.img
if (in_array($mime, array("application/x-tar"))) {
exec("/usr/bin/bsdtar xf " . escapeshellarg($dirfile) . " -C " . escapeshellarg($temp_dir), result_code: $retval);
if ($retval !== 0) {
echo "Failed in $dirfile\n";
}
} else if (in_array($mime, array("application/x-cpio"))) {
exec("cpio --extract --quiet --preserve-modification-time --make-directories --directory=" . escapeshellarg($temp_dir) . " < " . escapeshellarg($dirfile), result_code: $retval);
if ($retval !== 0) {
echo "Failed in $dirfile\n";
}
} else if ($mime === "application/x-installshield" && strtolower(pathinfo($dirfile)["extension"]) != "hdr") {
exec("unshield -d " . escapeshellarg($temp_dir) . " x " . escapeshellarg($dirfile), result_code: $retval);
if ($retval !== 0) {
// Try old compression method
exec("unshield -O -d " . escapeshellarg($temp_dir) . " x " . escapeshellarg($dirfile), result_code: $retval);
if ($retval !== 0) {
echo "Failed in $dirfile\n";
}
}
} else if ($mime === "application/octet-stream" && (str_contains($magic, "filesystem data") || str_starts_with($magic, "Minix filesystem"))) {
exec("/sbin/mount -o loop,ro " . escapeshellarg($dirfile) . " " . escapeshellarg($temp_dir), result_code: $retval);
if ($retval === 0) {
$isMount = true;
} else {
echo "Failed in $dirfile\n";
}
} else if (! in_array($mime, array("text/plain", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.visio", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.oasis.opendocument.presentation", "application/vnd.oasis.opendocument.spreadsheet", "application/vnd.oasis.opendocument.text", "application/x-ole-storage", "application/vnd.ms-office")) &&
! in_array($file, array("Thumbs.db"))) {
exec("7zz x -stxAPM -stxBase64 -stxCOFF -stxFLV -stxELF -stxGPT -stxIHex -stxLP -stxMBR -stxMachO -stxMub -stxPE -stxSWF -stxSWFc -stxUEFIc -stxUEFIf -stxHash -snoi -snld -o" . escapeshellarg($temp_dir) . " " . escapeshellarg($dirfile) . " 2>/dev/null", result_code: $retval);
}
if ($retval != 0) {
$xw->startElement("file");
storeCommonAttrs($file, $time, $stats);
$xw->writeElement("magic", $magic);
$xw->writeElement("mime", $mime);
$xw->endElement();
} else {
$xw->startElement("archive");
storeCommonAttrs($file, $time, $stats);
$xw->writeElement("magic", $magic);
$xw->writeElement("mime", $mime);
$xw->startElement("contents");
processFiles($temp_dir);
$xw->endElement();
$xw->endElement();
}
if ($isMount == true) {
exec("/sbin/umount " . escapeshellarg($temp_dir), result_code: $retval);
}
exec(sprintf("rm -rf %s", escapeshellarg($temp_dir)));
} else {
$xw->startElement("special");
storeCommonAttrs($file, $time, $stats);
$xw->writeElement("mime", $mode);
$xw->endElement();
}
}
}
closedir($dh);
}
}
$finfo = finfo_open(FILEINFO_NONE);
// Make sure symlinks are deleted first
if (file_exists($argv[3])) {
unlink($argv[3]);
}
$xw->openUri($argv[4]);
$xw->setIndent(true);
$xw->startDocument();
$xw->writePi("xml-stylesheet", "type=\"text/xsl\" href=\"imageviewer.xsl\"");
$xw->startElement("image");
$xw->startElement("title");
$xw->text($argv[1]);
$xw->endElement();
$xw->startElement("desc");
$xw->writeRaw($argv[3]);
$xw->endElement();
$xw->startElement("magic");
$xw->text($argv[2]);
$xw->endElement();
$xw->writeElement("iconref", "");
$xw->startElement("contents");
processFiles(".");
$xw->endElement();
$xw->endDocument();
finfo_close($finfo);
?>' | php -- "${title}" "${rootDesc}" "${header}" "${outfile}"
}
generateFileListing() {
outfile="$(pwd)/${outputName}${suffix}.xml"
pushd "${mnt}" >/dev/null
if [ -z "${linksTo}" ]; then
processFiles
popd >/dev/null
else
popd >/dev/null
cp "${linksTo}${suffix}.xml" "${outfile}"
linksTo=""
fi
sed -i 's#^ .*# '${icon}'#' "${outfile}"
umount "${mnt}"
}
generateFileListingLoop() {
generateHeader "${dev}"
for blockdev in "${dev}"*; do
if mount -o ro "${blockdev}" "${mnt}" 2>/dev/null; then
partition="${blockdev#$dev}"
if [ -n "${partition}" ]; then
suffix="-${partition}"
rootDesc="$(file -bs "${blockdev}")"
fi
echo "Device: ${blockdev}"
generateFileListing
fi
done
}
generateFileListingOptical() {
generateHeader "${dev}"
# Try various possible file systems and check for differences (Hybrid Disc)
if mount -t udf -o ro "${dev}" "${mnt}" 2>/dev/null; then
echo "Found UDF file system"
suffix="-udf"
generateFileListing
fi
if isoinfo -d -i "${dev}" 2>/dev/null | grep -q -e '^Joliet .* found.$'; then
mount -t iso9660 -o ro,norock "${dev}" "${mnt}"
echo "Found ISO 9660 file system with Joliet extensions"
suffix="-iso9660-joliet"
generateFileListing
fi
if isoinfo -d -i "${dev}" 2>/dev/null | grep -q -e 'Rock Ridge signatures .* found'; then
mount -t iso9660 -o ro,nojoliet "${dev}" "${mnt}"
echo "Found ISO 9660 file system with Rock Ridge signatures"
suffix="-iso9660-rock"
generateFileListing
fi
if [ ! -e "${outputName}-iso9660-joliet.xml" -a ! -e "${outputName}-iso9660-rock.xml" ] && mount -t iso9660 -o ro "${dev}" "${mnt}" 2>/dev/null; then
echo "Found ISO 9660 file system"
suffix="-iso9660"
generateFileListing
fi
mount -t tmpfs tmpfs "${mnt}"
if /opt/hfsexplorer/bin/unhfs -o "${mnt}" -resforks APPLEDOUBLE "${dev}" 2>/dev/null; then
if mount -t hfsplus -o ro "${dev}" "${mnt}" 2>/dev/null; then
echo "Found HFS+ file system"
suffix="-hfsplus"
umount "${mnt}"
else
echo "Found HFS file system"
suffix="-hfs"
fi
generateFileListing
else
umount "${mnt}"
fi
}
initLoop() {
dev="$(losetup -f)"
losetup -P "${dev}" "${image}"
}
processDisc() {
echo "Processing ${image[@]}..."
imagedata="${image}"
imagedata="${imagedata/.cue/.bin}"
imagedata="${imagedata/.toc/.bin}"
parseHtaccess "${imagedata}"
initOptical
generateFileListingOptical
teardownOptical
}
initOptical() {
if [ -L "${imagedata}" ]; then
linksTo="$(readlink "${imagedata}")"
linksTo="${linksTo%%.*}.${image##*.}"
fi
dev=""
# cdemu's dbus interface returns before the disc is actually unloaded
# (resulting in an error when trying to mount the same device again),
# so try again until is succeeds as a workaround
while ! cdemu load ${devnum} "${image[@]}"; do
sleep 0.5
done
while [ -z "${dev}" ]; do
dev="$(cdemu device-mapping | tail -1 | awk '{print $2}')"
sleep 0.5
done
}
teardownLoop() {
losetup -d "${dev}"
}
teardownOptical() {
cdemu unload ${devnum}
true
}
cleanup() {
umount "${mnt}" 2>/dev/null || true
umount -R "${tmpdir}"
rm -rf "${tmpdir}"
cdemu remove-device
exit
}
processImage() {
outputName="${image}"
# Magnetical media images
if [[ $image == *\.img ]] || [[ $image == *\.st ]] || [[ $image == *\.adf ]]; then
echo "Processing ${image}..."
parseHtaccess "${image}"
initLoop
generateFileListingLoop
teardownLoop
# Optical disc images
elif [[ $image == *\.cue ]] || [[ $image == *\.iso ]] || [[ $image == *\.raw ]]; then
# Multisession discs
if [[ $image == *_session1\.cue ]]; then
outputName="${image%%_session1.cue}.toc"
image=(${image%%_session1.cue}_session*.toc)
elif [[ $image == *_session*\.cue ]]; then
return
fi
processDisc
fi
}
trap 'cleanup' 0
tmpdir=$(mktemp -d -t generateFileListing-XXXXXXXXXX)
mount -t tmpfs tmpfs "${tmpdir}"
export TMPDIR="${tmpdir}"
mnt="${tmpdir}/mountpoint"
mkdir "${mnt}"
cdemu add-device
devnum=$(cdemu device-mapping | tail -1 | cut -f 1 -d " ")
if [ -z "$1" ]; then
for image in *.{img,st,adf,cue,iso,raw}; do
processImage "${image}"
done
else
for image; do
processImage "${image}"
done
fi
echo "Done!"