#!/bin/bash
#
# CGI interface to dpkg. Show information about debian packages.
#
# Copyright © 1999-2006 Massimo Dal Zotto <dz@debian.org>
# Copyright © 2017, 2019 Guillem Jover <guillem@debian.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

PROG_VERSION=2.x
SELF=${0##*/}; export SELF
HELP_PAGE="/usr/share/man/man8/dpkg-www.8.gz"
COLUMNS=256; export COLUMNS
SECTION_DESCRIPTIONS="
admin		Administrative utilities (install software, manage users, etc)
base		Basic needed utilities of every Debian system
cli-mono	Mono and the Common Language Infrastructure
comm		Programs for faxmodems and other communication devices
database	Database servers and tools
debug		Debug symbols
devel		Utilities and programs for software development
doc		Documentation and specialized programs for viewing documentation
editors		Text editors and word processors
education	Software, documentation or data related to educational activities
electronics	Programs for working with circuits and electronics
embedded	Programs for embedded systems
fonts		Fonts and font utilities
games		Games, toys, and fun programs
gnome		The GNOME Desktop Environment
gnu-r		The GNU R statistical computation and graphics system
gnustep		The GNUstep Environment
golang		Go programming language, libraries, and development tools
graphics	Utilities to create, view, and edit graphics files
hamradio	Software for ham radio operators
haskell		Haskell programming language and libraries
httpd		Webservers and their modules
interpreters	Interpreters for interpreted languages and macro processors
introspection	Introspection support for programming languages
java		Java programming language and libraries
javascript	JavaScript programming language, libraries, and development tools
kde		The KDE Desktop Environment
kernel		Kernel and kernel modules
libdevel	Development files for libraries
libs		Collections of software routines
lisp		Lisp programming language and libraries
localization	Language packs
mail		Programs to write, send, and route email messages
math		Numeric analysis and other mathematics-related software
metapackages	Packages which solely depend on other packages
misc		Miscellaneous software
net		Programs to connect to and provide various services
news		Usenet clients and servers
ocaml		OCaml programming language and libraries
oldlibs		Obsolete libraries
otherosfs	Emulators and software to read foreign filesystems
perl		Perl programming language and libraries
php		PHP programming language and libraries
python		Python programming language and libraries
ruby		Ruby programming language and libraries
rust		Rust programming language, library crates, and development tools
science		Software for scientific work
shells		Command shells and alternative console environments
sound		Utilities to play and record sound
tex		The TeX typesetting system
text		Text processing utilities
utils		Various system utilities
vcs		Version control systems
video		Utilities to record, view, edit, and stream video files
web		Web browsers, servers, proxies, and other tools
x11		The X window system and related software
xfce		The Xfce Desktop Environment
zope		Zope/Plone framework
"

TASK_DESCRIPTIONS="
basic-desktop	X window system
c-dev		C and C++
chinese-s	simplified Chinese environment
chinese-t	traditional Chinese environment
cyrillic	Cyrillic environment
database-server	SQL database
desktop		desktop environment
dialup		dialup system
dns-server	DNS server
file-server	file server
fortran-dev	fortran
french		French environment
games		games
german		German environment
japanese	Japanese environment
junior		Debian Jr.
korean		Korean environment
laptop		laptop system
mail-server	mail server
news-server	usenet news server
polish		Polish environment
print-server	print server
python-dev	Python
russian		Russian environment
science		scientific applications
spanish		Spanish environment
tcltk-dev	Tcl/Tk
tex		TeX/LaTeX environment
unix-server	conventional unix server
web-server	web server
"

# Configuration defaults, can be overridden in /etc/dpkg-www.conf
#
SHOW_LOCAL_FILES=auto	# Show local files directly (automatic)
LIST_UNAVAILABLE=0	# List also unavailable packages
LIST_DOCUMENTATION=1	# List registered package documentation
DPKG=auto		# Select the dpkg program (automatic)
MAN=auto		# Select the man program (automatic)
DEBIAN_CONTENTS=	# List of Contents-xxx.gz files
INSTALLED_ONLY=0	# Only installed packages
TASK_DESCRIPTIONS_FILE=/usr/share/tasksel/debian-tasks.desc
APT_CACHE_OPTS="-o APT::Cache::AllVersions=0"
CHECK_PACKAGE_VERSION=true
DPKG_WWW_BROWSER_HACK=false
GREP_DCTRL_OPTS="-i"
BGCOLOR="#c0c0c0"
KEEP_INPUT=0
DEBUG=0
DWWW_PATH="/cgi-bin/dwww"
INFO2WWW_PATH="/cgi-bin/info2www"

# Install buttons support. This require that users define the following
# mime action in their netscape mailcap file:
#
# application/dpkg-www-installer;/usr/sbin/dpkg-www-installer -f '%s'
#
CHECK_BUTTONS=0		# Enable install check-buttons in package list
INSTALL_BUTTON=0	# Enable install button in package info

# Source optional local configuration file
test -e /etc/dpkg-www.conf && . /etc/dpkg-www.conf 2>/dev/null
set -o noglob

HOSTNAME_FULL="$(hostname -f)"
HOSTNAME="${HOSTNAME_FULL%%.*}"

# Use dlocate, if installed, to speedup certain queries
if [ ! "$DPKG" -o "$DPKG" = auto ]; then
    if [ -x /usr/bin/dlocate ]; then
	DPKG=dlocate
    else
	DPKG=dpkg-query
    fi
fi

# Select the Manpage to HTML translator
if [ "$MAN" = dwww ]; then
	MAN="${DWWW_PATH}?type=man\\\\&location="
fi
if [ "$MAN" = man2html ]; then
    if [ -x /usr/lib/cgi-bin/man/man2html ]; then
	MAN="/cgi-bin/man/man2html?"
    elif [ -x /usr/lib/cgi-bin/man2html ]; then
	MAN="/cgi-bin/man2html?"
    else
	MAN="${DWWW_PATH}?type=man\\\\&location="
    fi
fi
if [ ! "$MAN" -o "$MAN" = auto ]; then
    if [ -x /usr/lib/cgi-bin/man/man2html ]; then
	MAN="/cgi-bin/man/man2html?"
    elif [ -x /usr/lib/cgi-bin/man2html ]; then
	MAN="/cgi-bin/man2html?"
    else
	MAN="${DWWW_PATH}?type=man\\\\&location="
    fi
fi

# If remote host is localhost generate local URL's for local files
case "$REMOTE_HOST" in
    localhost|$HOSTNAME|$HOSTNAME_FULL)
	if [ ! "$SHOW_LOCAL_FILES" -o "$SHOW_LOCAL_FILES" = auto ]; then
	    SHOW_LOCAL_FILES=true
	fi
	if [ "$HTTP_USER_AGENT" != "${HTTP_USER_AGENT#*Mozilla/[56]}" ]; then
	    # It seems that Mozilla/5.0 refuses to access local files from
	    # a web page for security reasons.
	    SHOW_LOCAL_FILES=false
	fi
	;;
    *)
	SHOW_LOCAL_FILES=false
	;;
esac

# Print a short help for the cgi-program
usage() {
    cat <<- EOF
	<P>
	You can query information about Debian packages and installed files.
	The search argument can be:
	<P>
	<TABLE WIDTH="80%">
	  <TR><TD>
	    <TD> <TT>?</TT>
	    <TD> show this help page
	  <TR><TD>
	    <TD> <TT>*</TT>
	    <TD> list all packages available on your system
	  <TR><TD>
	    <TD> <I>&lt;empty&gt;</I>
	    <TD> list all packages installed on your system
	  <TR><TD>
	    <TD> <I>&lt;space&gt;</I>
	    <TD> print only the input form, for use from wm menus
	  <TR><TD>
	    <TD> <I>package</I>
	    <TD> list the required package and its owned files
	  <TR><TD>
	    <TD> <I>list of packages</I>
	    <TD> list required packages concisely
	  <TR><TD>
	    <TD> <I>wildcard expression</I>
	    <TD> list matching packages concisely
	  <TR><TD>
	    <TD> <I>absolute pathname</I>
	    <TD> list packages owning pathname
	  <TR><TD>
	    <TD> <I>field=value</I>
	    <TD> list packages with control field matching value
	  <TR><TD>
	    <TD> <I>=value</I>
	    <TD> list packages with any field matching value
	  <TR><TD>
	    <TD> <I>section=?</I>
	    <TD> list available package sections
	  <TR><TD>
	    <TD> <I>task=?</I>
	    <TD> list available tasks
	  <TR><TD>
	    <TD> <I>recent=[&lt;N&gt;]</I>
	    <TD> list packages installed in last N days (default 1)
	</TABLE>
	<P>
	<H3>Examples:</H3>
	<P>
	<FORM METHOD="GET" ACTION="$SELF">
	<TABLE WIDTH="80%">
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=*"><TT>*</TT></A>
	    <TD> list all packages available on your system
	  <TR><TD>
	    <TD> <A HREF="$SELF?query="><I>&lt;empty&gt;</I></A>
	    <TD> list all packages installed on your system
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=binutils"><TT>binutils<TT></A>
	    <TD> list verbosely the package binutils
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=dpkg+apt"><TT>dpkg apt</TT></A>
	    <TD> list the dpkg and apt packages
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=jpeg*"><TT>jpeg*</TT></A>
	    <TD> list packages with name beginning with "jpeg"
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=*jpeg*"><TT>*jpeg*</TT></A>
	    <TD> list packages with name containing "jpeg"
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=/usr/bin/tail"><TT>/usr/bin/tail</TT></A>
	    <TD> list packages owning the file /usr/bin/tail
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=/.*/postfix"><TT>/.*/postfix</TT></A>
	    <TD> list packages owning any file named "postfix" (1)
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=/.*pbm.*"><TT>/.*pbm.*</TT></A>
	    <TD> list packages owning any file matching *pbm* (1)
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=maintainer%3DJoey+Hess"
	         ><TT>maintainer=Joey Hess</TT></A>
	    <TD> list packages with maintainer Joey Hess (2)
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=section%3Dnet"><TT>section=net</TT></A>
	    <TD> list packages with section net (2)
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=%3Dfirewall"><TT>=firewall</TT></A>
	    <TD> list packages with any field matching "firewall" (2)
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=section%3D?"><TT>section=?</TT></A>
	    <TD> list available package sections
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=task%3D?"><TT>task=?</TT></A>
	    <TD> list available tasks
	  <TR><TD>
	    <TD> <A HREF="$SELF?query=recent%3D2"><TT>recent=2</TT></A>
	    <TD> list packages installed in last 2 days
	  <TR><TD>
	    <TD> <TT>
	         <INPUT TYPE="hidden" NAME="packages" VALUE="hello">
		 <INPUT TYPE="submit" NAME="install" VALUE="Install">
		 package <A HREF="$SELF?query=hello">hello</A><TT>
	    <TD> install the package hello (3)
	  <TR><TD>
	    <TD> <TT>
		 <!-- INPUT TYPE="hidden" NAME="packages" VALUE="hello" -->
		 <INPUT TYPE="submit" NAME="remove" VALUE="Remove ">
		 package <A HREF="$SELF?query=hello">hello</A><TT>
	    <TD> remove the package hello (3)
	</TABLE>
	</FORM>
	<P>
	(1) The regexp form can be used to query also packages owning files
	not installed on your system. However this works only if you have
	properly configured the variable <TT>DEBIAN_CONTENTS</TT> in the
	configuration file<EM>&nbsp/etc/dpkg-www.conf</EM>.
	This query can be used to find which package you should install
	in order to get some missing command or file.
	Note that the leading '<TT>/</TT>' is required to distinguish filename
	queries from package queries.
	<P>
	(2) This works only if the
	<A HREF="$SELF?query=grep-dctrl">grep-dctrl</A> package is installed.
	</P>
	(3) This works only if <TT>INSTALL_BUTTON=1</TT> has been set in the
	configuration file<EM>&nbsp/etc/dpkg-www.conf</EM>, your browser has
	been configured to handle web installation requests and, of course,
	you know the root password.
	See <A HREF="${MAN}${HELP_PAGE}">dpkg-www(8)</A>
	man page for more information.
	</P>
EOF
}

debug() {
    test_flag "$DEBUG" && echo "<TT>$(escape_html "$*")</TT><BR>"
}

test_flag() {
    case "$1" in
	0|false|n|no|"") return 1 ;;
	*)		 return 0 ;;
    esac
}

# Sanity check on input values
checkInput() {
    if echo "$*" | grep -q '[(){}<>$%#&!|\\;`]'; then
	printError "Error: invalid input"
	exit
    fi
}

# Execute the query
doQuery() {
    debug "doQuery $1"
    local query="$1"
    case "${query%/}" in
	\?|-\?|-h|-help|--help)
	    usage
	    ;;
	" ")
	    return
	    ;;
	-*)
	    printError "Invalid keyword: $query"
	    exit
	    ;;
	/*)
	    queryOwners "$query"
	    ;;
	*=*)
	    queryPackagesByField "$query"
	    ;;
	*)
	    checkInput "$query"
	    queryPackages "$query"
	    ;;
    esac
}

# Query one or more packages
queryPackages() {
    debug "queryPackages $*"
    local packages="$*"
    local package="${packages%% *}"
    if [ ! "$packages" ]; then
	# No argument, show the package list
	debug "no arguments"
	listPackages
    elif [ "$package" != "$packages" -o "$packages" != "${packages#*[\[*?]}" ]
    then
	# Package list or wildcard, list packages concisely
	debug "package list"
	listPackages $packages
    else
	# One package, show package information
	debug "one package"
	listPackage $package
    fi
}

# Query one or more packages by control field value
queryPackagesByField() {
    field="${*%%=*}"; arg="${*#$field}"; arg="${arg#=}"
    field="${field## }"; field="${field%% }"
    arg="${arg## }"; arg="${arg%% }"
    if [ "$field" = section -a "$arg" = "?" ]; then
	listSections
	return
    fi
    if [ "$field" = task -a "$arg" = "?" ]; then
	listTasks
	return
    fi
    if [ "$field" = recent -o "$field" = new ]; then
	listRecentPackages $arg
	return
    fi
    if [ "$field" != "" -a ! -x /usr/bin/grep-dctrl ]; then
	echo "<PRE>"
	echo -n "Error: the <A HREF=\"$SELF?query=/usr/bin/grep-dctrl\">"
	echo -n "grep-dctrl</A>"
	echo " command is not installed on your system"
	echo "</PRE>"
	return
    fi
    echo "<PRE>"
    echo "Packages with $(escape_html "${field:-any field matching} $arg")"
    echo ""
    {
	if [ -x /usr/bin/grep-dctrl ]; then
	    ( dpkg-query -s; dpkg-query -p ) \
	       | grep-dctrl $GREP_DCTRL_OPTS -s Package -F "$field" "$arg"
	else
	    apt-cache search "$arg" | sed 's/ .*//;s/^/Package: /'
	fi
	dpkg-query -l "*" 2>&1
    } \
    | awk '
	/^Package:/ { p[$2]=1; NR=0; next }
	(NR <= 5) { print }
	(p[$2] != 1) { next }
	{ print }' \
    | listFilter
    echo "</PRE>"
}

# List available sections
listSections() {
    sections=$(
	( dpkg-query -s; dpkg-query -p ) | grep -h "^Section:" \
	   | sed 's|.*:[ ]*||; s|.*/||' | sort | uniq | grep -v "^non-"
    )
    echo "<pre>"
    echo "Available sections:"
    echo ""
    for s in $sections; do
	desc=$(
	    echo "$SECTION_DESCRIPTIONS" | sed "/^$s/!d; s/^$s[	 ]*//;q"
	)
	desc=${desc:="no description available"}
	x=$(printf "%$[16-${#s}]s" " ")
	echo "  <a href=\"${SELF}?query=section%3D${s}\">${s}</a>${x} ${desc}"
    done
    echo "</pre>"
}

# List available tasks
listTasks() {
    tasks=$(
	( dpkg-query -s; dpkg-query -p ) | grep -h "^Task:" \
	   | sed 's|.*:[ ]*||; s|,[ ]*|\
|g' | sort | uniq
    )
    echo "<pre>"
    echo "Available tasks:"
    echo ""
    for t in $tasks; do
	desc=$(
	    if [ -e "$TASK_DESCRIPTIONS_FILE" ]; then
		cat "$TASK_DESCRIPTIONS_FILE" 2>/dev/null | awk -v t="$t" '
		    /^Task:/ { task = $2 }
		    /^Description:/ && (task == t) {
			sub($1"[ ]*",""); print; exit
		    }'
	    else
		echo "$TASK_DESCRIPTIONS" | sed "/^$t/!d; s/^$t[ 	]*//;q"
	    fi
	)
	desc=${desc:="no description available"}
	x=$(printf "%$[16-${#t}]s" " ")
	echo "  <a href=\"${SELF}?query=task%3D${t}\">${t}</a>${x} ${desc}"
    done
    echo "</pre>"
}

# List recent packages
listRecentPackages() {
    n=${1:-1}
    s=$(
	N=$n perl -MTime::Piece -MTime::Seconds -E '\
	my $o = localtime->truncate(to => "day") - \
	Time::Seconds->new($ARGV[0] * ONE_DAY); \
	print $o->epoch;' \
    )
    packages=$(
	dpkg-query -f '${db-fsys:Last-Modified} ${binary:Package}\n' -W \
	   | S=$s perl -n -E '(@field) = split " "; \
		print "$field[1] " if $field[0] > $ENV{S};' \
    )
    if [ "$packages" ]; then
	echo "<PRE>"
	echo "Recent packages in last $n days:"
	echo ""
	dpkg-query -l $packages 2>&1 | listFilter
	echo "</PRE>"
    else
	echo "<PRE>"
	echo "No recent packages in last $n days"
	echo "</PRE>"
    fi
}

# List one or more packages concisely
listPackages() {
    debug "listPackages $*"
    echo "<PRE>"
    dpkg-query -l "$@" 2>&1 | listFilter
    echo "</PRE>"
}

# List a single package
#
listPackage() {
    debug "listPackage $*"
    local package="$1"
    local installed
    local status
    local version
    local latest
    local priority
    local essential

    pkg_info="$(dpkg-query -s $package)"
    if [ ! "$pkg_info" ]; then
	# Unknown package or virtual package
	listVirtualPackage $package || listUnknownPackage $package
	return 1
    fi

    status="$(echo "$pkg_info" | awk '/^Status:/ {print $4}')"
    #   installed		The package is unpacked and configured OK.
    #   half-installed		The installation has started but not completed.
    #   not-installed		The package is not installed on your system.
    #   unpacked		The package is unpacked, but not configured.
    #   half-configured		The package configuration has not completed.
    #   config-files		Only the config files exist on the system.
    # Handle unknown package
    if [ ! "$status" ]; then
	listUnknownPackage $package
	return 1
    fi

    if [ "$status" = not-installed -o "$status" = config-files ]; then
	if [ -x /usr/bin/apt-cache ]; then
	    pkg_info="$(
		echo "$pkg_info" \
		   | grep "^\(Package\|Status\|Priority\|Section\):"
		apt-cache $APT_CACHE_OPTS show $package 2>/dev/null \
		   | sed 's/^installed-size:/Installed-Size:/' \
		   | grep -v -e "^\(Package\|Status\|Priority\|Section\):" \
			     -e "^\(Filename\|MD5sum\|Size\):"
	    )"
	else
	    pkg_info="$(
		echo "$pkg_info" \
		   | grep "^\(Package\|Status\|Priority\|Section\):"
		dpkg-query --print-avail $package 2>/dev/null \
		   | grep -v -e "^\(Package\|Status\|Priority\|Section\):" \
			     -e "^\(Filename\|MD5sum\|Size\):"
	    )"
	fi
    fi

    # Check for newer version of installed package
    if [ "$status" = installed ]; then
	version="$(echo "$pkg_info" | awk '/^Version:/ {print $2}')"
	priority="$(echo "$pkg_info"  | grep ^Priority:  | awk '{print $2}')"
	essential="$(echo "$pkg_info" | grep ^Essential: | awk '{print $2}')"
	if test_flag "$CHECK_PACKAGE_VERSION"; then
	    latest=$(
		apt-cache $APT_CACHE_OPTS show $package \
		  | grep ^Version: | awk '{print $2}'
	    ); latest=${latest:-$version}
	    if dpkg --compare-versions "$latest" gt "$version"; then
		pkg_info="$(
		    echo "$pkg_info" \
		       | sed "/^Version:/s/\$/ (latest is $latest)/"
		)"
	    fi
	else
	    latest="$version"
	fi
    fi

    echo "<PRE>"

    # Print package info
    echo "$pkg_info" | packageFilter $package

    if [ "$INSTALL_BUTTON" = "top" ]; then
	addButtons
    fi

    # Print documentation and file list
    if [ "$status" != not-installed ]; then
	listPackageDocumentation $package
	listPackageFiles $package
    fi

    if [ "$INSTALL_BUTTON" != "top" ]; then
	addButtons
    fi

    echo "</PRE>"
}

# Add "Install", "Update" and "Remove" buttons if enabled.
addButtons() {
    if [ "$status" = installed ]; then
	if [ "$priority" = required -o "$essential" = yes ]; then
	    if [ "$version" != "$latest" ]; then
		addUpgradeButton $package $latest
	    fi
	else
	    if [ "$version" != "$latest" ]; then
		addRemoveUpgradeButtons $package $latest
	    else
		addRemoveButton $package
	    fi
	fi
    else
	addInstallButton $package
    fi
}

# Print a "Package not installed" message and add the "Install" button
# if enabled in the configuration file.
#
addInstallButton() {
    debug "addInstallButton $*"
    local package="$1"
    local pkgname="$package"
    if test_flag "$INSTALL_BUTTON"; then
	echo "<FORM METHOD=\"GET\" ACTION=\"$SELF\">" \
	     "<INPUT TYPE=\"hidden\" NAME=\"packages\" VALUE=\"$pkgname\">"
	echo -n \
	     "Package $(escape_html "$package") is not installed." \
	     "<INPUT TYPE=\"submit\" NAME=\"install\" VALUE=\"Install\">" \
	     "</FORM>"
    else
	echo ""
	echo "Package $(escape_html "$package") is not installed."
    fi
}

# Add the "Remove" button if enabled in the configuration file.
#
addRemoveButton() {
    debug "addRemoveButton $*"
    local package="$1"
    local pkgname="$package"
    if test_flag "$INSTALL_BUTTON"; then
	echo "<FORM METHOD=\"GET\" ACTION=\"$SELF\">" \
	     "<INPUT TYPE=\"hidden\" NAME=\"packages\" VALUE=\"$pkgname\">"
	echo -n \
	     "<INPUT TYPE=\"submit\" NAME=\"remove\" VALUE=\"Remove\">" \
	     "package $package" \
	     "</FORM>"
    fi
}

# Add the "Upgrade" button if enabled in the configuration file.
#
addUpgradeButton() {
    debug "addUpgradeButton $*"
    local package="$1"
    local latest="$2"
    local pkgname="$package"
    if test_flag "$INSTALL_BUTTON"; then
	echo "<FORM METHOD=\"GET\" ACTION=\"$SELF\">" \
	     "<INPUT TYPE=\"hidden\" NAME=\"packages\" VALUE=\"$pkgname\">"
	echo -n \
	     "<INPUT TYPE=\"submit\" NAME=\"install\" VALUE=\"Upgrade\">" \
	     "package $package to version $latest" \
	     "</FORM>"
    fi
}

# Add the "Remove" and "Upgrade" buttons if enabled in the configuration file.
#
addRemoveUpgradeButtons() {
    debug "addRemoveUpgradeButtons $*"
    local package="$1"
    local latest="$2"
    local pkgname="$package"
    if test_flag "$INSTALL_BUTTON"; then
	echo "<FORM METHOD=\"GET\" ACTION=\"$SELF\">" \
	     "<INPUT TYPE=\"hidden\" NAME=\"packages\" VALUE=\"$pkgname\">"
	echo -n \
	     "<INPUT TYPE=\"submit\" NAME=\"remove\" VALUE=\"Remove\">" \
	     "package $package. " \
	     "<INPUT TYPE=\"submit\" NAME=\"install\" VALUE=\"Upgrade\">" \
	     "to version $latest" \
	     "</FORM>"
    fi
}

# List all packages providing a virtual package.
#
listVirtualPackage() {
    debug "listVirtualPackage $*"
    local package="$1"
    local packages=$(
	( dpkg-query -s; dpkg-query -p ) \
	  | awk -v package=$package '
	    /^Package:/ { owner = $2 }
	    /^Provides:/ {
		gsub(",","")
		for (i=2; i<=NF; i++) { if ($i == package) { print owner } }
	    }' \
	  | sort | uniq
    )
    test "$packages" || return 1
    debug "is virtual package"
    echo "<PRE>"
    echo "Virtual package $(escape_html "$package") is provided by:"
    echo "</PRE>"
    listPackages $(echo $packages)
}

listUnknownPackage() {
    debug "listUnknownPackage $*"
    local package="$(escape_html "$1")"
    echo "<PRE>"
    echo "Package $package is not installed and no info is available."
    echo "</PRE>"
}

listPackageFiles() {
    debug "listPackageFiles $*"
    local package="$1"
    echo
    echo "Files owned by package $(escape_html "$package"):"
    echo
    $DPKG -L $package | sort | fileFilter
}

# This is really ugly but I don't know a better way since install-docs
# doesn't record package ownership of documents.
#
listPackageDocumentation() {
    debug "listPackageDocumentation $*"
    local package="$1"
    local installdocs_files
    local document_id
    local f
    test_flag "$LIST_DOCUMENTATION" || return
    test -x /usr/sbin/install-docs || return
    dpkg-query --control-list $package | grep -q postinst || return
    installdocs_files=$(
	dpkg-query --control-show $package postinst \
	  | grep 'install-docs[ 	]*-i' \
	  | sed 's/.*install-docs[ 	]*-i[ 	]*//;s/[ 	].*//' \
	  | sort | uniq
    )
    debug "installdocs_files=$installdocs_files"
    test "$installdocs_files" || return
    echo
    echo "Documentation:"
    echo
    for f in $installdocs_files; do
	# Hack for postinsts with `$package' in filenames
	test ! -e $f && f="$(echo "$f" | sed "s/\$package/$package/i")"
	# Get document_id (this can be different than file basename)
	if [ -e $f ]; then
	    document_id="$(grep "^Document:" "$f" | sed 's/.*: //' | head -1)"
	else
	    document_id="${f##*/}"
	fi
	debug "document_id=$document_id"
	title="$(
	    /usr/sbin/install-docs -s "$document_id" 2>/dev/null \
	      | awk '/^Title: / { sub("^Title: *",""); print $0 }'
	)"
	/usr/sbin/install-docs -s "$document_id" 2>/dev/null \
	  | awk '
	    /^Format:/ { format = $2 }
	    /^Index: / { print $2 "\t" format }
	    /^Files: / { if (!match($2,"\\*")) { print $2 "\t" format } }' \
	  | sort | uniq | fileFilter "$title" | sed 's/^/ /'
    done
}

# Find all packages owning a file. Must be able to handle things like:
#
#   /usr/lib/games/maelstrom/Maelstrom Fonts
#   /usr/share/ucblogo/logolib/#
#   /usr/share/ucblogo/logolib/`
#   /etc/diskless-image/config.sh
#   /bin/bash
#   /.*/postfix
#   /.*pbm.*
#
queryOwners() {
    debug "queryOwners $*"
    local file="${1%/}"
    local owners=$(
	# Must grep the exact filename when using dlocate
	$DPKG -S "$file" 2>/dev/null | grep ": $file$" | cut -f1 -d: \
	    | grep -v diversion | tr -d ','
    )
    if [ "$owners" ]; then
	echo "<PRE>"
	echo "Packages owning $(escape_html "$file"):"
	echo "</PRE>"
	listPackages $owners
	return
    fi

    if [ "$DEBIAN_CONTENTS" ]; then
	owners=$(
	    zcat $DEBIAN_CONTENTS 2>/dev/null \
	       | grep "^${file#/}[	 ][ 	]*[^ ]*\$" \
	       | sed "s^${file#/}[	 ][ 	]*; s/,/\\
/"	       | sed 's|.*/||' \
	       | sort | uniq
	)
    fi
    if [ "$owners" ]; then
	echo "<PRE>"
	echo "Packages owning $(escape_html "$file"):"
	echo "</PRE>"
	listPackages $owners
	return
    fi

    echo "<PRE>"
    echo "No package is owning $(escape_html "$file")"
    echo "</PRE>"
}

# Convert package info to html by adding hrefs to other packages.
#
packageFilter() {
    local package="$1"
    local uri=""
    if [ "$DPKG_WWW_BROWSER_HACK" = true -a "$SHOW_LOCAL_FILES" ]; then
	uri="$(packageURI "$package")"
	test "$uri" = "${uri#file:/}" && uri=""
    fi
    awk -v SELF=$SELF -v package="$package" -v uri="$uri" '
	{ gsub("<","\\&lt;"); gsub(">","\\&gt;") }
	/^Package:/ {
	    # Add link to the Debian package page for this package
	    p = s = package; gsub("\\+", "%2B", s)
	    u = "https://packages.debian.org"
	    $2 = sprintf("<a href=\"%s/%s\">%s</a>", u,s,p)
	}
	/^Section:/ {
	    # Add link to package section list
	    s = t = r = $2
	    gsub("\\+", "\\+", r)
	    gsub("\\+", "%2B", s)
	    sub(r, "<a href=\"" SELF "?query=section%3D" s "\">" t "</a>", $2)
	}
	/^Task:/ {
	    # Add links to package tasks list
	    for (f=2; f<=NF; f++) {
		r = $f;
		gsub(",", "", r)
		s = t = r
		gsub("\\+", "\\+", r)
		gsub("\\+", "%2B", s)
		sub(r, "<a href=\"" SELF "?query=task%3D" s "\">" t "</a>", $f)
            }
	}
	/^Maintainer:/ {
	    # Add link to maintainer packages overview
	    m = $0; sub(".*: ","",m);sub(" &lt;.*","",m);gsub("[()]","\\\\&",m)
	    e = $0; sub(".*&lt;","",e);sub("&gt;.*","",e)
	    u = "https://qa.debian.org/developer.php?login=" e
	    sub(m, "<a href=\"" u "\">&</a>")

	    # Add link to Debian BTS page for this package
	    s = package; gsub("\\+", "%2B", s)
	    d = "[Debian Bug Report]"
	    u = "https://bugs.debian.org/cgi-bin/pkgreport.cgi"
	    h = sprintf("<a href=\"%s?repeatmerged=no&pkg=%s\">%s</a>", u,s,d)
	    $0 = $0 " " h
	}
	/^(Depends|Pre-Depends|Conflicts|Replaces|Provides|Suggests|\
	  |Recommends|Enhances):/\
	{
	    # Workaround for apt-cache missing space:
	    #   Depends: python-slang(>= 0.2.0), python (>= 2.1)
	    gsub("\\(", " (")
	    for (f=2; f<=NF; f++) {
		if ($f == "|") {
		    continue
		}
		if (match($f,"^\\(")) {
		    f++;
		    continue
		}
		r = $f;
		gsub(",", "", r)
		s = t = r
		gsub("\\+", "\\+", r)
		gsub("\\+", "%2B", s)
		sub(r, "<a href=\"" SELF "?query=" s "\">" t "</a>", $f)
            }
        }
	/^Description:/ {
	    if (uri != "") {
		s = package
		t = uri
		gsub("\\+", "%2B", s)
		gsub("^file:", "", t)
		h =  SELF "?browse=Browse&packages=" s
		printf("Filename: <a href=\"%s\">%s</a>\n", h, t)
	    }
	}
	/^Version:/ {
	    if (match($0, "latest is")) {
		# Show latest version in bold
		sub("\\(latest is", "<B>(latest is"); sub("\\)", ")</B>")
	    }
	}
	/^$/ { next}
	/^ .$/ { $0 = " " }
	{ print }'
}

# Convert a dpkg package list to html adding hrefs to package names.
# This is used for both file list and documentation list.
#
listFilter() {
    awk -v SELF=$SELF \
	-v CHECK_BUTTONS=$CHECK_BUTTONS \
	-v DPKG_AVAILABLE=$DPKG_AVAILABLE \
	-v LIST_UNAVAILABLE=$LIST_UNAVAILABLE \
	-v INSTALLED_ONLY=$INSTALLED_ONLY '
	BEGIN {
	    is_hdr  = 1
	    statMax = 3
	    nameMax = 10
	    versMax = 10
	    descMax = 10
	    if (CHECK_BUTTONS) {
		pkg_button = \
		    "<INPUT TYPE=\"checkbox\" NAME=\"packages\" VALUE=\"%s\">"
		install_button = \
		    "<INPUT TYPE=\"submit\" NAME=\"install\" " \
			"VALUE=\"Install selected packages\">"
		reset_button = \
		    "<INPUT TYPE=\"reset\">"
		buttonW = 2
	    }
	}
	/^No packages found matching/ {
	    print $0
	    missing++
	    NR--
	    next
	}
	(is_hdr) {
	    header[NR] = $0
	}
	(is_hdr == 1) && ($1 == "||/") {
	    # ||/ Name           Version        Description
	    hstat  = $1
	    hname  = $2
	    hvers  = $3
	    hdesc  = $4
	    is_hdr = 2
	    next
	}
	(is_hdr == 2) {
	    # +++-==============-==============-===============================
	    split($0,A,"-")
	    statLen = length(A[1])
	    nameLen = length(A[2])
	    versLen = length(A[3])
	    hsep    = A[4] A[4] A[4]
	    is_hdr  = 0
	    next
	}
	(!is_hdr) {
	    # ii  dpkg-www       2.52           package description ...
	    stat = substr($0,1,statLen)
	    name = substr($0,5,nameLen)
	    vers = substr($0,6+nameLen,versLen)
	    desc = substr($0,7+nameLen+versLen)
	    sub("[ ]+$","",name)
	    sub("[ ]+$","",vers)
	    sub("[ ]+$","",desc)
	    if (INSTALLED_ONLY && !match(stat,"^(i.|.c)")) {
	        next
	    }
	    # if (vers == "<none>") { vers = "-" }
	    nameMax = max(nameMax, length(name))
	    versMax = max(versMax, length(vers))
	    descMax = max(descMax, length(desc))
	    packages[NR,"stat"] = stat
	    packages[NR,"name"] = name
	    packages[NR,"vers"] = vers
	    packages[NR,"desc"] = desc
	    if (desc == "(no description available)") {
		needs_desc = missing_desc[name] = NR
	    }
	}
	END {
	    if (needs_desc) {
		update_descs()
	    }
	    if (NR < 6) {
		if (!missing) {
		    print "no packages found"
		}
		exit
	    }
	    if (missing) {
		print ""
	    }
	    if (CHECK_BUTTONS) {
		printf "<FORM METHOD=\"GET\" ACTION=\"" SELF "\">"
	    }
	    for (i=1; i<=3; i++) {
		print header[i]
	    }
	    descMax = max(descMax,80-statMax-nameMax-versMax-3)
	    printf("%s %s %s %s\n", \
		   pad(hstat,statMax+buttonW), pad(hname,nameMax), \
		   pad(hvers,versMax), hdesc)
	    printf("+++%s-%s-%s-%s\n", substr("--",1,buttonW), \
		   pad(hsep,nameMax), pad(hsep,versMax), pad(hsep,descMax))
	    nr = NR
	    npackages = 0
	    for (i=6; i<=nr; i++) {
		name = packages[i,"name"]
		if (!name) { continue }
		if (CHECK_BUTTONS) {
		    if (substr(packages[i,"stat"],2,1) == "n") {
			stat = pad(packages[i,"stat"],statMax)
			button = sprintf(pkg_button, name",")
			install = 1
		    } else {
			stat = pad(packages[i,"stat"],statMax)
			button = "  "
		    }
		} else {
		    stat = pad(packages[i,"stat"],statMax)
		}
		npad = pad("", nameMax-length(name))
		vers = escape(pad(packages[i,"vers"],versMax))
		desc = escape(packages[i,"desc"])
		if ((LIST_UNAVAILABLE == 0) &&
		    (desc == "(no description available)")) {
		    NR--
		    continue
		}
		href = link = name
		gsub("\\+", "%2B", href)
		href = "<A HREF=\"" SELF "?query=" href "\">" link "</A>" npad
		printf("%s%s %s %s %s\n", stat, button, href, vers, desc)
		npackages++
	    }
	    if (npackages > 0) {
		npad = statMax+2+buttonW+nameMax
		print ""
		printf(pad(sprintf("%d packages", npackages), npad))
	    }
	    if (CHECK_BUTTONS) {
		if (install) {
		    printf reset_button "  " install_button "\n"
		}
		printf "</FORM>"
	    }
	}
	function escape(s) {
	    gsub("&","\\&amp;",s)
	    gsub("<","\\&lt;",s)
	    gsub(">","\\&gt;",s)
	    return s
	}
	function pad(s,l) {
	    while (length(s) < l) {
		s = s "          "
	    }
	    return substr(s,1,l)
	}
	function max(x,y) {
	    return ((x > y) ? x : y)
	}
	function update_descs() {
	    while (getline < DPKG_AVAILABLE) {
		if ($1 == "Package:") {
		    if ((nr=missing_desc[$2])) {
			package = $2
		    } else {
			package = ""
		    }
		}
		if (package && ($1 == "Version:")) {
		    sub("Version: ",""); vers = $0
		    packages[nr,"vers"] = vers
		    versMax = max(versMax, length(vers))
		}
		if (package && ($1 == "Description:")) {
		    sub("Description: ",""); desc = $0
		    packages[nr,"desc"] = desc
		    descMax = max(descMax, length(desc))
		}
	    }
	}'
}

# Convert a file list to html adding hrefs to files in /usr/{doc,info,man}.
#
fileFilter() {
    awk -v title="$*" -v show_local_files="$SHOW_LOCAL_FILES" \
	-v man="$MAN" -v info2www="$INFO2WWW_PATH" -v dwww="$DWWW_PATH" '
	/\/usr\/(share\/)?([a-zA-Z0-9_+-]+\/)?man\/.*\..*/ {
	    if (title) { t = title } else { t = $1 }
	    if ($2) { t = t " [" $2 "]"; NF=1 }
	    r = s = $1
	    gsub("\\+", "\\+", r);
	    gsub("\\+", "%2B", s);
	    gsub("\\$","\\$",r)
	    sub(r, "<A HREF=\"" man s "\">" t "</A>")
	    print
	    next
	}
	/\/usr\/(share\/)?info\/.*\.gz/ {
	    if (title) { t = title } else { t = $1 }
	    if ($2) { t = t " [" $2 "]"; NF=1 }
	    r = s = $1
	    gsub("\\+", "\\+", r);
	    gsub("\\+", "%2B", s);
	    gsub("\\$","\\$",r)
	    sub(r, "<A HREF=\"" info2www "?" s "\">" t "</A>")
	    print
	    next
	}
	/\/usr\/(doc|share\/(doc|gtk-doc|gnome\/(html|help)|RFC))\// {
	    if (title) { t = title } else { t = $1 }
	    if ($2) { t = t " [" $2 "]"; NF=1 }
	    r = s = $1
	    gsub("\\+", "\\+", r);
	    gsub("\\+", "%2B", s);
	    gsub("\\$","\\$",r)
	    if (show_local_files == "true") {
		sub(r, "<A HREF=\"file:" s "\">" t "</A>")
	    } else {
		sub(r, "<A HREF=\"" dwww "?type=file\\&location=" s "\">"\
		       t "</A>")
	    }
	    print
	    next
	}
	/^\/.$/ { next }
	/^$/ { next }
	/^[^\/]/ { sub("^","  ") }
	{ print }'
}

packageURI() {
    local package="$1"
    local pkgcache=$(tempfile)
    local srcpkgcache=$(tempfile)
    trap "rm -f $pkgcache $srcpkgcache" 0
    apt-get -o "Debug::NoLocking=1" \
	    -o "Dir::Cache=/tmp" \
	    -o "Dir::Cache::pkgcache=$pkgcache" \
	    -o "Dir::Cache::srcpkgcache=$srcpkgcache" \
	    -o "Dir::Cache::archives=/var/cache/apt/archives" \
	    install --reinstall --print-uris -y "$package" \
      | grep -F "/${package}_" \
      | sed "/^'/!d; s/^'//; s/'.*//"
    rm -f $pkgcache $srcpkgcache 2>/dev/null
}

printInstallRequest() {
    local packages="$1"
    local host="$HOSTNAME_FULL"
    cat <<-EOF
	<!--
	Install-host: $host
	Install-packages: $packages
	-->
	<H1>dpkg-www Web Installation</H1>
	<P>
	If you are reading this document on your browser or you have
	been asked to save it to disk instead of running the installation
	of the required packages this means that your browser has not
	been set up correctly for dpkg-www web installation.
	See <A HREF="${MAN}${HELP_PAGE}">dpkg-www(8)</A>
	man page for more information.
	</P>
EOF
}

printRemoveRequest() {
    local packages="$1"
    local host="$HOSTNAME_FULL"
    cat <<-EOF
	<!--
	Remove-host: $host
	Remove-packages: $packages
	-->
	<H1>dpkg-www Web Deinstallation</H1>
	<P>
	If you are reading this document on your browser or you have
	been asked to save it to disk instead of running the installation
	of the required packages this means that your browser has not
	been set up correctly for dpkg-www web installation.
	See <A HREF="${MAN}${HELP_PAGE}">dpkg-www(8)</A>
	man page for more information.
	</P>
EOF
}

printBrowseRequest() {
    local package="$1"
    local host="$HOSTNAME_FULL"
    cat <<-EOF
	<!--
	Browse-package: $package
	-->
	<H1>dpkg-www Browse Request</H1>
	<P>
	If you are reading this document on your browser or you have
	been asked to save it to disk instead of running a browser
	on the required packages this means that your browser has not
	been set up correctly for dpkg-www web installation.
	See <A HREF="${MAN}${HELP_PAGE}">dpkg-www(8)</A>
	man page for more information.
	</P>
EOF
}

# Get the value of a query argument
getValue() {
    local key="$1"
    test -x /usr/bin/perl || return
    perl -e "use CGI; my \$q = new CGI(\$_); print scalar \$q->param('$key')" "$1"
}

escape_html() {
    echo "$*" | sed 's/&/\&amp;/g; s/\"/\&quot;/g; s/</\&lt;/g; s/>/\&gt;/g'
}

printHtmlHeader() {
    local title="${1:-Debian Packages}"
    local content_type="${2:-text/html; charset=UTF-8}"
    test "$html_header_done" && return
    html_header_done=true
    echo "Content-type: $content_type"
    echo "Connection: close"
    echo ""
    if [ "$content_type" ]; then
	echo "<HTML>"
	echo "<HEAD>"
	echo "<TITLE>$title</TITLE>"
	echo "</HEAD>"
	echo "<BODY BGCOLOR=\"${BGCOLOR:-#c0c0c0}\">"
    fi
}

# Generate an input form or the <isindex> tag
printInputForm() {
    local default
    local installed
    test "$noheader" = true && return
    test "$input_form_done" = true  && return
    test "$KEEP_INPUT" = 1 && default="$query"
    test "$INSTALLED_ONLY" = 1 && installed="checked"
    echo "<H1>Debian packages on $HOSTNAME</H1>"
    if [ -x /usr/bin/perl ]; then
	echo '<P>'
	if [ "${HTTP_USER_AGENT#Lynx}" != "$HTTP_USER_AGENT" ]; then
	   echo "<FORM METHOD=\"GET\" ACTION=\"$SELF\">"
	    echo "Search:"
	    echo '<INPUT NAME="query" SIZE="42" VALUE="'"$default"'">'
	   echo '<INPUT NAME="search" TYPE="submit" VALUE="Submit">'
	   echo '(?&nbsp;for&nbsp;help)<BR>'
	   echo '<INPUT NAME="installed" TYPE="checkbox"' $installed '>' \
		'only installed packages' '<br>&nbsp;<br>'
	   echo '</FORM>'
	else
	   echo "<FORM METHOD=\"GET\" ACTION=\"$SELF\">"
	   echo '<INPUT NAME="search" TYPE="submit" VALUE="  Search  ">'
	    echo '<INPUT NAME="query" SIZE="50" VALUE="'"$default"'">'
	   echo '<INPUT NAME="help" TYPE="submit" VALUE="   Help   ">'
	   echo '<INPUT NAME="installed" TYPE="checkbox"' $installed '>' \
		'only installed packages'
	   echo '</FORM>'
	   test "$query" != " " && echo '<HR WIDTH="100%">'
	fi
	echo '</P>'
    else
	echo '<P>'
	echo '<ISINDEX>'
	echo '</P>'
    fi
    input_form_done=true
}

# Print bottom info
printHtmlBottom() {
    test "$noheader" = true && return
    cat <<- EOF
	<P>
	<HR WIDTH="100%">
	<ADDRESS>
	Generated by <A HREF="$SELF?query=dpkg-www">
	dpkg-www $PROG_VERSION</A>
	</ADDRESS>
	<BR>
	</P>
        $(test "${tempfile}" && echo "<PRE>" && cat $tempfile | sed 's/</\&lt;/g' && echo "</PRE>")
	</BODY>
	</HTML>
EOF
}

printError() {
    local message="$*"
    printHtmlHeader "$title"
    printInputForm
    echo "<P>"
    echo "$(escape_html "$message")"
    echo "<P>"
    usage
    printHtmlBottom
}

#------------------------------------------------------------------------------

# Get the query arguments
if [ "$*" ]; then
    # ISINDEX or interactive invocation
    debug=""
    query="$*"
    help=""
    install=""
    remove=""
    browse=""
    packages=""
    bgcolor=""
    noheader=""
    installed=""
else
    # GET method
    debug="$(getValue debug)"
    query="$(getValue query)"
    help="$(getValue help)"
    install="$(getValue install)"
    remove="$(getValue remove)"
    browse="$(getValue browse)"
    packages="$(getValue packages)"
    bgcolor="$(getValue bgcolor)"
    noheader="$(getValue noheader)"
    installed="$(getValue installed)"
fi
test "$help"    != "" && query="?"
test "$debug"   != "" && DEBUG="$debug"
test "$bgcolor" != "" && BGCOLOR="$bgcolor"

# Print debugging info
if test_flag "$DEBUG"; then
    printHtmlHeader "$title"
    cat <<- EOF
	<PRE>
	$(env | sort)
	SELF=$SELF
	DPKG=$DPKG
	SHOW_LOCAL_FILES=$SHOW_LOCAL_FILES
	LIST_DOCUMENTATION=$LIST_DOCUMENTATION
	CHECK_BUTTONS=$CHECK_BUTTONS
	INSTALL_BUTTON=$INSTALL_BUTTON
	PROG=$0
	ARGC=$#
	ARGV="$(escape_html "$*")"
	query="$(escape_html "$query")"
	install="$(escape_html "$install")"
	remove="$(escape_html "$remove")"
	browse="$(escape_html "$browse")"
	packages="$(escape_html "$packages")"
	bgcolor="$(escape_html "$bgcolor")"
	noheader="$(escape_html "$noheader")"
	installed="$(escape_html "$installed")"
	debug="$(escape_html "$debug")"
	</PRE>
EOF
    tempfile=$(tempfile); trap "rm -f $tempfile" 0
    exec 2>$tempfile
    set -x
fi

# Sanity check on input values. $query is checked in doQuery.
checkInput "$install$remove$packages$debug"

if [ "$install" -a "$remove" ]; then
    printError "Error: invalid request"
    exit
fi
if [ "$install$remove$browse" -a "$packages" = "" ]; then
    printError "Error: invalid request"
    exit
fi
if [ "$install$remove$browse" -a "$query" != "" ]; then
    printError "Error: invalid request"
    exit
fi
if [ "$install" -a "$install" != "Install" -a "$install" != "Upgrade" ]; then
    printError "Error: invalid request"
    exit
fi
if [ "$remove" -a "$remove" != "Remove" ]; then
    printError "Error: invalid request"
    exit
fi
if [ "$browse" -a "$browse" != "Browse" ]; then
    printError "Error: invalid request"
    exit
fi
if [ "$installed" != "" ]; then
    if test_flag "$installed"; then
       INSTALLED_ONLY=1
    else
       INSTALLED_ONLY=0
    fi
fi

title="Debian Packages${query:+: $query}"

if [ "$install" ]; then
    printHtmlHeader "Package Installation" application/dpkg-www-installer
    printInstallRequest "$packages"
    printHtmlBottom
elif [ "$remove" ]; then
    printHtmlHeader "Package Deinstallation" application/dpkg-www-installer
    printRemoveRequest "$packages"
    printHtmlBottom
elif [ "$browse" ]; then
    printHtmlHeader "Package Browser" application/dpkg-www-browser
    printBrowseRequest "$packages"
    printHtmlBottom
else
    printHtmlHeader "$title"
    printInputForm
    doQuery "$query"
    printHtmlBottom
fi

# end of file
