#!/bin/bash
#
# The Qubes OS Project, http://www.qubes-os.org
#
# Copyright (C) 2013  Joanna Rutkowska <joanna@invisiblethingslab.com>
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
#
# Requires: 
# - ImageMagick (convert)

INPUT_FILE="$1"
RCVD_FILE=$(mktemp --tmpdir qpdf-conversion-XXXXXXXX)
CONVERTED_FILE="$(dirname "$1")/$(basename "$1" .pdf).trusted.pdf"
CONVERTED_FILE_PARTIAL="$CONVERTED_FILE".part.pdf


MAX_PAGES=10000
MAX_IMG_WIDTH=10000
MAX_IMG_HEIGHT=10000
IMG_DEPTH=8
MAX_IMG_SIZE=$((MAX_IMG_WIDTH * MAX_IMG_HEIGHT * 3))

VERBOSE=1
if [ -n "$PROGRESS_FOR_GUI" ]; then
    VERBOSE=0;
fi

die() {
    reason="$1"
    if [ -n "$PROGRESS_FOR_GUI" ]; then
        zenity --error --title="PDF conversion error" --text="$reason"
    else
        echo "$reason" >&2
    fi
    exit 1
}


# Send the input (untrusted) file to the server...
[ $VERBOSE -ge 1 ] && echo "-> Sending file to a Disposable VM..." >&2
cat "$INPUT_FILE"
exec >&-

# ... and get the recvd *simple* representation:

# Note: the server might be compromised at this point so, it can very well send
# us something else than the simple representation.  Thus we explicitly specify
# input format to ImageMagick's convert via "rgb:" prefix, forcing it to
# interpret whatever stream of bytes it gets on input as a simple RGB array. We
# hope that when using this RGB format explicitly (which is the simplest format
# for bitmaps in the known universe), there is no space for offending bug in
# image parsing...

# First, get the no of pages:
read -r NO_PAGES
if [[ ! "$NO_PAGES" =~ ^[1-9][0-9]*$ ]] || [[ $NO_PAGES -le 0 ]] || [[ $NO_PAGES -gt $MAX_PAGES ]] ; then
    die "The remote party return invalid no of pages, aborting!"
fi

[ $VERBOSE -ge 1 ] && echo "-> Waiting for converted samples..." >&2

PAGE=1
while [ $PAGE -le "$NO_PAGES" ]; do
    read -r IMG_WIDTH IMG_HEIGHT
    if [ $VERBOSE -eq 1 ]; then
        echo -n "-> Receiving page $PAGE out of $NO_PAGES..." >&2
        printf "\r" >&2
    elif [ $VERBOSE -gt 1 ]; then    
        echo "-> Receiving page $PAGE out of $NO_PAGES..." >&2
    fi
    if [[ ! "$IMG_WIDTH" =~ ^[1-9][0-9]*$ ]] || [ "$IMG_WIDTH" -le 0 ] || [ "$IMG_WIDTH" -gt $MAX_IMG_WIDTH ] || \
       [[ ! "$IMG_HEIGHT" =~ ^[1-9][0-9]*$ ]] || [ "$IMG_HEIGHT" -le 0 ] || [ "$IMG_HEIGHT" -gt $MAX_IMG_HEIGHT ]; then
        die "The remote party return invalid image geometry info, aborting!"
    fi
    [ $VERBOSE -ge 2 ] && echo "--> page geometry: $IMG_WIDTH x $IMG_HEIGHT x $IMG_DEPTH" >&2
    IMG_SIZE=$((IMG_WIDTH*IMG_HEIGHT*3))
    if [ $IMG_SIZE -le 0 ] || [ $IMG_SIZE -gt $MAX_IMG_SIZE ]; then
        die "Calculated image size is invalid, aborting!"
    fi
    # save the simplified RGB image into a temp PDF file:
    RGB_FILE=$RCVD_FILE-$PAGE.rgb
    PNG_FILE=$RCVD_FILE-$PAGE.png
    PDF_FILE=$RCVD_FILE-$PAGE.pdf
    head -c $IMG_SIZE > "$RGB_FILE"
    RCVD_IMG_SIZE=$(stat -c %s "$RGB_FILE")
    if [ "$RCVD_IMG_SIZE" -ne $IMG_SIZE ]; then
        die "The remote party return invalid no of bytes of the RGB file, aborting!"
    fi
    # here, the important part is that we *explicitly* specify RGB as the input format via "rgb:"
    # We first convert to a (compressed) PNG to create smaller output files
    convert_msgs=$(convert -size "${IMG_WIDTH}x${IMG_HEIGHT}" -depth ${IMG_DEPTH} rgb:"$RGB_FILE" png:"$PNG_FILE" 2>&1)
    # shellcheck disable=SC2181
    if [ $? -ne 0 ]; then
        die "Page $PAGE conversion failed (RGB->PNG): $convert_msgs"
    fi
    rm -f "$RGB_FILE"

    # now convert the (trusted but compressed) PNG into PDF for easy assembly...
    convert_msgs=$(convert "$PNG_FILE" "$PDF_FILE" 2>&1)
    # shellcheck disable=SC2181
    if [ $? -ne 0 ]; then
        die "Page $PAGE conversion failed (PNG->PDF): $convert_msgs"
    fi
    rm -f "$PNG_FILE"

    if [ $PAGE -gt 1 ]; then
        convert_msgs=$(pdfunite "$CONVERTED_FILE" "$PDF_FILE" "$CONVERTED_FILE_PARTIAL" 2>&1)
        # shellcheck disable=SC2181
        if [ $? -ne 0 ]; then
            die "Error merging converted page: $convert_msgs"
        fi
        mv "$CONVERTED_FILE_PARTIAL" "$CONVERTED_FILE" || die
    else
        mv "$PDF_FILE" "$CONVERTED_FILE" || die
    fi
    rm -f "$PDF_FILE" || die

    PAGE=$((PAGE+1))

    [ -n "$PROGRESS_FOR_GUI" ] && echo $(((PAGE - 1) * 90 / NO_PAGES)) >& "$SAVED_FD_1"
done

if [ $VERBOSE -eq 1 ]; then
    echo >&2
fi
 
[ $VERBOSE -ge 1 ] && echo "-> Converted PDF saved as: $CONVERTED_FILE" >&2

mkdir -p "$HOME/QubesUntrustedPDFs"
ORIG_FILE="$HOME/QubesUntrustedPDFs/$(basename "$INPUT_FILE")"
mv "$INPUT_FILE" "${ORIG_FILE}" || die "Moving original file failed"
[ $VERBOSE -ge 1 ] && echo "-> Original file saved as $ORIG_FILE" >&2

# Cleanup
rm -f "$RCVD_FILE"*
[ -n "$PROGRESS_FOR_GUI" ] && echo "100" >& "$SAVED_FD_1"
exit 0
