Move Horde_Mime and Horde_Crypt from hatchery.
authorMichael M Slusarz <slusarz@curecanti.org>
Wed, 10 Dec 2008 23:11:22 +0000 (16:11 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Wed, 10 Dec 2008 23:20:07 +0000 (16:20 -0700)
116 files changed:
framework/Crypt/lib/Horde/Crypt.php [new file with mode: 0644]
framework/Crypt/lib/Horde/Crypt/pgp.php [new file with mode: 0644]
framework/Crypt/lib/Horde/Crypt/smime.php [new file with mode: 0644]
framework/Crypt/package.xml [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/bug_6601.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/bug_6601.asc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/clear.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted_symmetric.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_private.asc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_public.asc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_signature.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed2.txt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/fixtures/smime_subjectAltName.pem [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp.inc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_decrypt.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_decrypt_symmetric.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_encrypt.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_encrypt_symmetric.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_encryptedSymmetrically.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_generateRevocation.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_getSignersKeyID.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_parsePGPData.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_pgpPacketInformation.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignature.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignatureByUidIndex.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_pgpPrettyKey.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_publicKeyMIMEPart.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_sign.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_skipif.inc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_verify.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/pgp_verifyPassphrase.phpt [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/smime.inc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/smime_skipif.inc [new file with mode: 0644]
framework/Crypt/test/Horde/Crypt/smime_subjectAltName.phpt [new file with mode: 0644]
framework/Mime/lib/Horde/Mime.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Address.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Headers.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Magic.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Mail.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Mdn.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Part.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/Driver.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/audio.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/css.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/deb.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/default.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/enriched.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/enscript.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/html.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/images.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/msexcel.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/mspowerpoint.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/msword.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/common.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/global_document.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/main_html.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/palm.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/style_header.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/style_inlined.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/style_mapping.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/table.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/table_cells.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/table_columns.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/ooo/table_rows.xsl [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/pdf.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/php.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/plain.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/rar.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/report.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/rfc822.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/richtext.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/rpm.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/rtf.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/security.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/simple.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/smil.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/source.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/srchighlite.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/tgz.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/tnef.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/vcard.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/webcpp.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/wordperfect.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/Viewer/zip.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/mime.magic.php [new file with mode: 0644]
framework/Mime/lib/Horde/Mime/mime.mapping.php [new file with mode: 0644]
framework/Mime/package.xml [new file with mode: 0644]
framework/Mime/test/Horde/Mime/AllTests.php [new file with mode: 0644]
framework/Mime/test/Horde/Mime/attachment.bin [new file with mode: 0644]
framework/Mime/test/Horde/Mime/bug_325.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/bug_325.txt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/bug_4834.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/bug_6896.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/contents.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/mail_001.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/mail_002.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/mail_003.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/mail_004.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/mail_005.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/mail_006.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/mail_007.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/mail_008.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/mail_dummy.inc [new file with mode: 0644]
framework/Mime/test/Horde/Mime/url.phpt [new file with mode: 0644]
framework/Mime/test/Horde/Mime/url1.html [new file with mode: 0644]
framework/Mime/test/Horde/Mime/url2.html [new file with mode: 0644]
framework/Mime/test/Horde/Mime/url3.html [new file with mode: 0644]
framework/Mime/test/Horde/Mime/url4.html [new file with mode: 0644]
framework/Mime/test/Horde/Mime/url5.html [new file with mode: 0644]
framework/Mime/test/Horde/Mime/url6.html [new file with mode: 0644]
framework/Mime/test/Horde/Mime/url7.html [new file with mode: 0644]
framework/Mime/test/Horde/Mime/viewer_php.phpt [new file with mode: 0644]

diff --git a/framework/Crypt/lib/Horde/Crypt.php b/framework/Crypt/lib/Horde/Crypt.php
new file mode 100644 (file)
index 0000000..1544ce6
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+/**
+ * The Horde_Crypt:: class provides an API for various cryptographic
+ * systems used by Horde applications.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Crypt
+ */
+class Horde_Crypt
+{
+    /**
+     * The temporary directory to use.
+     *
+     * @var string
+     */
+    protected $_tempdir;
+
+    /**
+     * Attempts to return a concrete Horde_Crypt instance based on $driver.
+     *
+     * @param mixed $driver  The type of concrete Horde_Crypt subclass to
+     *                       return. If $driver is an array, then we will look
+     *                       in $driver[0]/lib/Crypt/ for the subclass
+     *                       implementation named $driver[1].php.
+     * @param array $params  A hash containing any additional configuration or
+     *                       parameters a subclass might need.
+     *
+     * @return Horde_Crypt  The newly created concrete Horde_Crypt instance, or
+     *                      false on an error.
+     */
+    static public function factory($driver, $params = array())
+    {
+        if (is_array($driver)) {
+            list($app, $driv_name) = $driver;
+            $driver = basename($driv_name);
+        } else {
+            $driver = basename($driver);
+        }
+
+        /* Return a base Horde_Crypt object if no driver is specified. */
+        if (empty($driver) || (strcmp($driver, 'none') == 0)) {
+            return new Horde_Crypt();
+        }
+
+        $class = 'Horde_Crypt_' . $driver;
+        if (!empty($app)) {
+            $class = $app . '_' . $class;
+        }
+
+        if (!class_exists($class)) {
+            if (empty($app)) {
+                include_once dirname(__FILE__) . '/Crypt/' . $driver . '.php';
+            } else {
+                include_once $GLOBALS['registry']->get('fileroot', $app) . '/lib/Crypt/' . $driver . '.php';
+            }
+        }
+
+        return class_exists($class)
+            ? new $class($params)
+            : PEAR::raiseError('Class definition of ' . $class . ' not found.');
+    }
+
+    /**
+     * Attempts to return a reference to a concrete Horde_Crypt instance
+     * based on $driver. It will only create a new instance if no
+     * Horde_Crypt instance with the same parameters currently exists.
+     *
+     * This should be used if multiple crypto backends (and, thus,
+     * multiple Horde_Crypt instances) are required.
+     *
+     * This method must be invoked as: $var = &Horde_Crypt::singleton()
+     *
+     * @param mixed $driver  The type of concrete Horde_Crypt subclass to
+     *                       return. If $driver is an array, then we will look
+     *                       in $driver[0]/lib/Crypt/ for the subclass
+     *                       implementation named $driver[1].php.
+     * @param array $params  A hash containing any additional configuration or
+     *                       connection parameters a subclass might need.
+     *
+     * @return Horde_Crypt  The concrete Horde_Crypt reference, or false on an
+     *                      error.
+     */
+    static public function &singleton($driver, $params = array())
+    {
+        static $instances = array();
+
+        $signature = serialize(array($driver, $params));
+        if (!isset($instances[$signature])) {
+            $instances[$signature] = Horde_Crypt::factory($driver, $params);
+        }
+
+        return $instances[$signature];
+    }
+
+    /**
+     * Outputs error message if we are not using a secure connection.
+     *
+     * @return PEAR_Error  Returns a PEAR_Error object if there is no secure
+     *                     connection.
+     */
+    public function requireSecureConnection()
+    {
+        if ($GLOBALS['browser']->usingSSLConnection()) {
+            return;
+        }
+
+        if (!empty($GLOBALS['conf']['safe_ips'])) {
+            if (reset($GLOBALS['conf']['safe_ips']) == '*') {
+                return;
+            }
+
+            /* $_SERVER['HTTP_X_FORWARDED_FOR'] is user data and not
+             * reliable. We dont' consult it for safe IPs. We also
+             * have to assume that if it is present, the user is
+             * coming through a proxy server. If so, we don't count
+             * any non-SSL connection as safe, no matter the source
+             * IP. */
+            if (!isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+                $remote_addr = $_SERVER['REMOTE_ADDR'];
+                foreach ($GLOBALS['conf']['safe_ips'] as $safe_ip) {
+                    $safe_ip = preg_replace('/(\.0)*$/', '', $safe_ip);
+                    if (strpos($remote_addr, $safe_ip) === 0) {
+                        return;
+                    }
+                }
+            }
+        }
+
+        return PEAR::raiseError(_("The encryption features require a secure web connection."));
+    }
+
+    /**
+     * Encrypt the requested data.
+     * This method should be provided by all classes that extend Horde_Crypt.
+     *
+     * @param string $data   The data to encrypt.
+     * @param array $params  An array of arguments needed to encrypt the data.
+     *
+     * @return array  The encrypted data.
+     */
+    public function encrypt($data, $params = array())
+    {
+        return $data;
+    }
+
+    /**
+     * Decrypt the requested data.
+     * This method should be provided by all classes that extend Horde_Crypt.
+     *
+     * @param string $data   The data to decrypt.
+     * @param array $params  An array of arguments needed to decrypt the data.
+     *
+     * @return array  The decrypted data.
+     */
+    public function decrypt($data, $params = array())
+    {
+        return $data;
+    }
+
+    /**
+     * Create a temporary file that will be deleted at the end of this
+     * process.
+     *
+     * @access private
+     *
+     * @param string  $descrip  Description string to use in filename.
+     * @param boolean $delete   Delete the file automatically?
+     *
+     * @return string  Filename of a temporary file.
+     */
+    protected function _createTempFile($descrip = 'horde-crypt',
+                                       $delete = true)
+    {
+        return Util::getTempFile($descrip, $delete, $this->_tempdir, true);
+    }
+
+}
diff --git a/framework/Crypt/lib/Horde/Crypt/pgp.php b/framework/Crypt/lib/Horde/Crypt/pgp.php
new file mode 100644 (file)
index 0000000..a43d2f2
--- /dev/null
@@ -0,0 +1,1701 @@
+<?php
+
+/**
+ * Horde_Crypt_pgp:: provides a framework for Horde applications to interact
+ * with the GNU Privacy Guard program ("GnuPG").  GnuPG implements the OpenPGP
+ * standard (RFC 2440).
+ *
+ * GnuPG Website: http://www.gnupg.org/
+ *
+ * This class has been developed with, and is only guaranteed to work with,
+ * Version 1.21 or above of GnuPG.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Crypt
+ */
+class Horde_Crypt_pgp extends Horde_Crypt
+{
+    /**
+     * Armor Header Lines - From RFC 2440:
+     *
+     * An Armor Header Line consists of the appropriate header line text
+     * surrounded by five (5) dashes ('-', 0x2D) on either side of the header
+     * line text. The header line text is chosen based upon the type of data
+     * that is being encoded in Armor, and how it is being encoded.
+     *
+     *  All Armor Header Lines are prefixed with 'PGP'.
+     *
+     *  The Armor Tail Line is composed in the same manner as the Armor Header
+     *  Line, except the string "BEGIN" is replaced by the string "END."
+     */
+
+    /* Used for signed, encrypted, or compressed files. */
+    const ARMOR_MESSAGE = 1;
+
+    /* Used for signed files. */
+    const ARMOR_SIGNED_MESSAGE = 2;
+
+    /* Used for armoring public keys. */
+    const ARMOR_PUBLIC_KEY = 3;
+
+    /* Used for armoring private keys. */
+    const ARMOR_PRIVATE_KEY = 4;
+
+    /* Used for detached signatures, PGP/MIME signatures, and natures
+     * following clearsigned messages. */
+    const ARMOR_SIGNATURE = 5;
+
+    /* Regular text contained in an PGP message. */
+    const ARMOR_TEXT = 6;
+
+    /**
+     * Strings in armor header lines used to distinguish between the different
+     * types of PGP decryption/encryption.
+     *
+     * @var array
+     */
+    protected $_armor = array(
+        'MESSAGE' => self::ARMOR_MESSAGE,
+        'SIGNED MESSAGE' => self::ARMOR_SIGNED_MESSAGE,
+        'PUBLIC KEY BLOCK' => self::ARMOR_PUBLIC_KEY,
+        'PRIVATE KEY BLOCK' => self::ARMOR_PRIVATE_KEY,
+        'SIGNATURE' => self::ARMOR_SIGNATURE
+    );
+
+    /* The default public PGP keyserver to use. */
+    const KEYSERVER_PUBLIC = 'pgp.mit.edu';
+
+    /* The number of times the keyserver refuses connection before an error is
+     * returned. */
+    const KEYSERVER_REFUSE = 3;
+
+    /* The number of seconds that PHP will attempt to connect to the keyserver
+     * before it will stop processing the request. */
+    const KEYSERVER_TIMEOUT = 10;
+
+    /**
+     * The list of PGP hash algorithms (from RFC 3156).
+     *
+     * @var array
+     */
+    protected $_hashAlg = array(
+        1 => 'pgp-md5',
+        2 => 'pgp-sha1',
+        3 => 'pgp-ripemd160',
+        5 => 'pgp-md2',
+        6 => 'pgp-tiger192',
+        7 => 'pgp-haval-5-160',
+        8 => 'pgp-sha256',
+        9 => 'pgp-sha384',
+        10 => 'pgp-sha512',
+        11 => 'pgp-sha224',
+    );
+
+    /**
+     * GnuPG program location/common options.
+     *
+     * @var array
+     */
+    protected $_gnupg;
+
+    /**
+     * Filename of the temporary public keyring.
+     *
+     * @var string
+     */
+    protected $_publicKeyring;
+
+    /**
+     * Filename of the temporary private keyring.
+     *
+     * @var string
+     */
+    protected $_privateKeyring;
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  Parameter array containing the path to the GnuPG
+     *                       binary (key = 'program') and to a temporary
+     *                       directory.
+     */
+    function __construct($params = array())
+    {
+        $this->_tempdir = Util::createTempDir(true, $params['temp']);
+
+        if (empty($params['program'])) {
+            Horde::fatal(PEAR::raiseError('The location of the GnuPG binary must be given to the Horde_Crypt_pgp:: class.'), __FILE__, __LINE__);
+        }
+
+        /* Store the location of GnuPG and set common options. */
+        $this->_gnupg = array(
+            $params['program'],
+            '--no-tty',
+            '--no-secmem-warning',
+            '--no-options',
+            '--no-default-keyring',
+            '--yes',
+            '--homedir ' . $this->_tempdir
+        );
+
+        if (strncasecmp(PHP_OS, 'WIN', 3)) {
+            array_unshift($this->_gnupg, 'LANG= ;');
+        }
+    }
+
+    /**
+     * Generates a personal Public/Private keypair combination.
+     *
+     * @param string $realname    The name to use for the key.
+     * @param string $email       The email to use for the key.
+     * @param string $passphrase  The passphrase to use for the key.
+     * @param string $comment     The comment to use for the key.
+     * @param integer $keylength  The keylength to use for the key.
+     *
+     * @return array  An array consisting of the public key and the private
+     *                key, or PEAR_Error on error.
+     * <pre>
+     * Return array:
+     * Key            Value
+     * --------------------------
+     * 'public'   =>  Public Key
+     * 'private'  =>  Private Key
+     * </pre>
+     */
+    public function generateKey($realname, $email, $passphrase, $comment = '',
+                                $keylength = 1024)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Create temp files to hold the generated keys. */
+        $pub_file = $this->_createTempFile('horde-pgp');
+        $secret_file = $this->_createTempFile('horde-pgp');
+
+        /* Create the config file necessary for GnuPG to run in batch mode. */
+        /* TODO: Sanitize input, More user customizable? */
+        $input = array();
+        $input[] = '%pubring ' . $pub_file;
+        $input[] = '%secring ' . $secret_file;
+        $input[] = 'Key-Type: DSA';
+        $input[] = 'Key-Length: 1024';
+        $input[] = 'Subkey-Type: ELG-E';
+        $input[] = 'Subkey-Length: ' . $keylength;
+        $input[] = 'Name-Real: ' . $realname;
+        if (!empty($comment)) {
+            $input[] = 'Name-Comment: ' . $comment;
+        }
+        $input[] = 'Name-Email: ' . $email;
+        $input[] = 'Expire-Date: 0';
+        $input[] = 'Passphrase: ' . $passphrase;
+        $input[] = '%commit';
+
+        /* Run through gpg binary. */
+        $cmdline = array(
+            '--gen-key',
+            '--batch',
+            '--armor'
+        );
+        $result = $this->_callGpg($cmdline, 'w', $input, true, true);
+
+        /* Get the keys from the temp files. */
+        $public_key = file($pub_file);
+        $secret_key = file($secret_file);
+
+        /* If either key is empty, something went wrong. */
+        if (empty($public_key) || empty($secret_key)) {
+            $msg = _("Public/Private keypair not generated successfully.");
+            if (!empty($result->stderr)) {
+                $msg .= ' ' . _("Returned error message:") . ' ' . $result->stderr;
+            }
+            return PEAR::raiseError($msg, 'horde.error');
+        }
+
+        return array('public' => $public_key, 'private' => $secret_key);
+    }
+
+    /**
+     * Returns information on a PGP data block.
+     *
+     * @param string $pgpdata  The PGP data block.
+     *
+     * @return array  An array with information on the PGP data block. If an
+     *                element is not present in the data block, it will
+     *                likewise not be set in the array.
+     * <pre>
+     * Array Format:
+     * -------------
+     * [public_key]/[secret_key] => Array
+     *   (
+     *     [created] => Key creation - UNIX timestamp
+     *     [expires] => Key expiration - UNIX timestamp (0 = never expires)
+     *     [size]    => Size of the key in bits
+     *   )
+     *
+     * [fingerprint] => Fingerprint of the PGP data (if available)
+     *                  16-bit hex value (DEPRECATED)
+     * [keyid] => Key ID of the PGP data (if available)
+     *            16-bit hex value (as of Horde 3.2)
+     *
+     * [signature] => Array (
+     *     [id{n}/'_SIGNATURE'] => Array (
+     *         [name]        => Full Name
+     *         [comment]     => Comment
+     *         [email]       => E-mail Address
+     *         [fingerprint] => 16-bit hex value (DEPRECATED)
+     *         [keyid]       => 16-bit hex value (as of Horde 3.2)
+     *         [created]     => Signature creation - UNIX timestamp
+     *         [expires]     => Signature expiration - UNIX timestamp
+     *         [micalg]      => The hash used to create the signature
+     *         [sig_{hex}]   => Array [details of a sig verifying the ID] (
+     *             [created]     => Signature creation - UNIX timestamp
+     *             [expires]     => Signature expiration - UNIX timestamp
+     *             [fingerprint] => 16-bit hex value (DEPRECATED)
+     *             [keyid]       => 16-bit hex value (as of Horde 3.2)
+     *             [micalg]      => The hash used to create the signature
+     *         )
+     *     )
+     * )
+     * </pre>
+     *
+     * Each user ID will be stored in the array 'signature' and have data
+     * associated with it, including an array for information on each
+     * signature that has signed that UID. Signatures not associated with a
+     * UID (e.g. revocation signatures and sub keys) will be stored under the
+     * special keyword '_SIGNATURE'.
+     */
+    public function pgpPacketInformation($pgpdata)
+    {
+        $data_array = array();
+        $keyid = '';
+        $header = null;
+        $input = $this->_createTempFile('horde-pgp');
+        $sig_id = $uid_idx = 0;
+
+        /* Store message in temporary file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, $pgpdata);
+        fclose($fp);
+
+        $cmdline = array(
+            '--list-packets',
+            $input
+        );
+        $result = $this->_callGpg($cmdline, 'r');
+
+        foreach (explode("\n", $result->stdout) as $line) {
+            /* Headers are prefaced with a ':' as the first character on the
+               line. */
+            if (strpos($line, ':') === 0) {
+                $lowerLine = String::lower($line);
+
+                /* If we have a key (rather than a signature block), get the
+                   key's ID */
+                if (strpos($lowerLine, ':public key packet:') !== false ||
+                    strpos($lowerLine, ':secret key packet:') !== false) {
+                    $cmdline = array(
+                        '--with-colons',
+                        $input
+                    );
+                    $data = $this->_callGpg($cmdline, 'r');
+                    if (preg_match("/(sec|pub):.*:.*:.*:([A-F0-9]{16}):/", $data->stdout, $matches)) {
+                        $keyid = $matches[2];
+                    }
+                }
+
+                if (strpos($lowerLine, ':public key packet:') !== false) {
+                    $header = 'public_key';
+                } elseif (strpos($lowerLine, ':secret key packet:') !== false) {
+                    $header = 'secret_key';
+                } elseif (strpos($lowerLine, ':user id packet:') !== false) {
+                    $uid_idx++;
+                    $line = preg_replace_callback('/\\\\x([0-9a-f]{2})/', array($this, '_pgpPacketInformationHelper'), $line);
+                    if (preg_match("/\"([^\<]+)\<([^\>]+)\>\"/", $line, $matches)) {
+                        $header = 'id' . $uid_idx;
+                        if (preg_match('/([^\(]+)\((.+)\)$/', trim($matches[1]), $comment_matches)) {
+                            $data_array['signature'][$header]['name'] = trim($comment_matches[1]);
+                            $data_array['signature'][$header]['comment'] = $comment_matches[2];
+                        } else {
+                            $data_array['signature'][$header]['name'] = trim($matches[1]);
+                            $data_array['signature'][$header]['comment'] = '';
+                        }
+                        $data_array['signature'][$header]['email'] = $matches[2];
+                        $data_array['signature'][$header]['keyid'] = $keyid;
+                        // TODO: Remove in Horde 4
+                        $data_array['signature'][$header]['fingerprint'] = $keyid;
+                    }
+                } elseif (strpos($lowerLine, ':signature packet:') !== false) {
+                    if (empty($header) || empty($uid_idx)) {
+                        $header = '_SIGNATURE';
+                    }
+                    if (preg_match("/keyid\s+([0-9A-F]+)/i", $line, $matches)) {
+                        $sig_id = $matches[1];
+                        $data_array['signature'][$header]['sig_' . $sig_id]['keyid'] = $matches[1];
+                        $data_array['keyid'] = $matches[1];
+                        // TODO: Remove these 2 entries in Horde 4
+                        $data_array['signature'][$header]['sig_' . $sig_id]['fingerprint'] = $matches[1];
+                        $data_array['fingerprint'] = $matches[1];
+                    }
+                } elseif (strpos($lowerLine, ':literal data packet:') !== false) {
+                    $header = 'literal';
+                } elseif (strpos($lowerLine, ':encrypted data packet:') !== false) {
+                    $header = 'encrypted';
+                } else {
+                    $header = null;
+                }
+            } else {
+                if ($header == 'secret_key' || $header == 'public_key') {
+                    if (preg_match("/created\s+(\d+),\s+expires\s+(\d+)/i", $line, $matches)) {
+                        $data_array[$header]['created'] = $matches[1];
+                        $data_array[$header]['expires'] = $matches[2];
+                    } elseif (preg_match("/\s+[sp]key\[0\]:\s+\[(\d+)/i", $line, $matches)) {
+                        $data_array[$header]['size'] = $matches[1];
+                    }
+                } elseif ($header == 'literal' || $header == 'encrypted') {
+                    $data_array[$header] = true;
+                } elseif ($header) {
+                    if (preg_match("/version\s+\d+,\s+created\s+(\d+)/i", $line, $matches)) {
+                        $data_array['signature'][$header]['sig_' . $sig_id]['created'] = $matches[1];
+                    } elseif (isset($data_array['signature'][$header]['sig_' . $sig_id]['created']) &&
+                              preg_match('/expires after (\d+y\d+d\d+h\d+m)\)$/', $line, $matches)) {
+                        $expires = $matches[1];
+                        preg_match('/^(\d+)y(\d+)d(\d+)h(\d+)m$/', $expires, $matches);
+                        list(, $years, $days, $hours, $minutes) = $matches;
+                        $data_array['signature'][$header]['sig_' . $sig_id]['expires'] =
+                            strtotime('+ ' . $years . ' years + ' . $days . ' days + ' . $hours . ' hours + ' . $minutes . ' minutes', $data_array['signature'][$header]['sig_' . $sig_id]['created']);
+                    } elseif (preg_match("/digest algo\s+(\d{1})/", $line, $matches)) {
+                        $micalg = $this->_hashAlg[$matches[1]];
+                        $data_array['signature'][$header]['sig_' . $sig_id]['micalg'] = $micalg;
+                        if ($header == '_SIGNATURE') {
+                            /* Likely a signature block, not a key. */
+                            $data_array['signature']['_SIGNATURE']['micalg'] = $micalg;
+                        }
+                        if ($sig_id == $keyid) {
+                            /* Self signing signature - we can assume
+                             * the micalg value from this signature is
+                             * that for the key */
+                            $data_array['signature']['_SIGNATURE']['micalg'] = $micalg;
+                            $data_array['signature'][$header]['micalg'] = $micalg;
+                        }
+                    }
+                }
+            }
+        }
+
+        $keyid && $data_array['keyid'] = $keyid;
+        // TODO: Remove for Horde 4
+        $keyid && $data_array['fingerprint'] = $keyid;
+
+        return $data_array;
+    }
+
+    protected function _pgpPacketInformationHelper($a)
+    {
+        return chr(hexdec($a[1]));
+    }
+
+    /**
+     * Returns human readable information on a PGP key.
+     *
+     * @param string $pgpdata  The PGP data block.
+     *
+     * @return string  Tabular information on the PGP key.
+     */
+    public function pgpPrettyKey($pgpdata)
+    {
+        $msg = '';
+        $packet_info = $this->pgpPacketInformation($pgpdata);
+        $fingerprints = $this->getFingerprintsFromKey($pgpdata);
+
+        if (!empty($packet_info['signature'])) {
+            /* Making the property names the same width for all
+             * localizations .*/
+            $leftrow = array(_("Name"), _("Key Type"), _("Key Creation"),
+                             _("Expiration Date"), _("Key Length"),
+                             _("Comment"), _("E-Mail"), _("Hash-Algorithm"),
+                             _("Key ID"), _("Key Fingerprint"));
+            $leftwidth = array_map('strlen', $leftrow);
+            $maxwidth  = max($leftwidth) + 2;
+            array_walk($leftrow, array($this, '_pgpPrettyKeyFormatter'), $maxwidth);
+
+            foreach (array_keys($packet_info['signature']) as $uid_idx) {
+                if ($uid_idx == '_SIGNATURE') {
+                    continue;
+                }
+                $key_info = $this->pgpPacketSignatureByUidIndex($pgpdata, $uid_idx);
+
+                if (!empty($key_info['keyid'])) {
+                    $key_info['keyid'] = $this->_getKeyIDString($key_info['keyid']);
+                } else {
+                    $key_info['keyid'] = null;
+                }
+
+                $fingerprint = isset($fingerprints[$key_info['keyid']]) ? $fingerprints[$key_info['keyid']] : null;
+
+                $msg .= $leftrow[0] . (isset($key_info['name']) ? stripcslashes($key_info['name']) : '') . "\n"
+                    . $leftrow[1] . (($key_info['key_type'] == 'public_key') ? _("Public Key") : _("Private Key")) . "\n"
+                    . $leftrow[2] . strftime("%D", $key_info['key_created']) . "\n"
+                    . $leftrow[3] . (empty($key_info['key_expires']) ? '[' . _("Never") . ']' : strftime("%D", $key_info['key_expires'])) . "\n"
+                    . $leftrow[4] . $key_info['key_size'] . " Bytes\n"
+                    . $leftrow[5] . (empty($key_info['comment']) ? '[' . _("None") . ']' : $key_info['comment']) . "\n"
+                    . $leftrow[6] . (empty($key_info['email']) ? '[' . _("None") . ']' : $key_info['email']) . "\n"
+                    . $leftrow[7] . (empty($key_info['micalg']) ? '[' . _("Unknown") . ']' : $key_info['micalg']) . "\n"
+                    . $leftrow[8] . (empty($key_info['keyid']) ? '[' . _("Unknown") . ']' : $key_info['keyid']) . "\n"
+                    . $leftrow[9] . (empty($fingerprint) ? '[' . _("Unknown") . ']' : $fingerprint) . "\n\n";
+            }
+        }
+
+        return $msg;
+    }
+
+    protected function _pgpPrettyKeyFormatter(&$s, $k, $m)
+    {
+        $s .= ':' . str_repeat(' ', $m - String::length($s));
+    }
+
+    protected function _getKeyIDString($keyid)
+    {
+        /* Get the 8 character key ID string. */
+        if (strpos($keyid, '0x') === 0) {
+            $keyid = substr($keyid, 2);
+        }
+        if (strlen($keyid) > 8) {
+            $keyid = substr($keyid, -8);
+        }
+        return '0x' . $keyid;
+    }
+
+    /**
+     * Returns only information on the first ID that matches the email address
+     * input.
+     *
+     * @param string $pgpdata  The PGP data block.
+     * @param string $email    An e-mail address.
+     *
+     * @return array  An array with information on the PGP data block. If an
+     *                element is not present in the data block, it will
+     *                likewise not be set in the array.
+     * <pre>
+     * Array Fields:
+     * -------------
+     * key_created  =>  Key creation - UNIX timestamp
+     * key_expires  =>  Key expiration - UNIX timestamp (0 = never expires)
+     * key_size     =>  Size of the key in bits
+     * key_type     =>  The key type (public_key or secret_key)
+     * name         =>  Full Name
+     * comment      =>  Comment
+     * email        =>  E-mail Address
+     * fingerprint  =>  16-bit hex value (DEPRECATED)
+     * keyid        =>  16-bit hex value
+     * created      =>  Signature creation - UNIX timestamp
+     * micalg       =>  The hash used to create the signature
+     * </pre>
+     */
+    public function pgpPacketSignature($pgpdata, $email)
+    {
+        $data = $this->pgpPacketInformation($pgpdata);
+        $key_type = null;
+        $return_array = array();
+
+        /* Check that [signature] key exists. */
+        if (!isset($data['signature'])) {
+            return $return_array;
+        }
+
+        /* Store the signature information now. */
+        if (($email == '_SIGNATURE') &&
+            isset($data['signature']['_SIGNATURE'])) {
+            foreach ($data['signature'][$email] as $key => $value) {
+                $return_array[$key] = $value;
+            }
+        } else {
+            $uid_idx = 1;
+
+            while (isset($data['signature']['id' . $uid_idx])) {
+                if ($data['signature']['id' . $uid_idx]['email'] == $email) {
+                    foreach ($data['signature']['id' . $uid_idx] as $key => $val) {
+                        $return_array[$key] = $val;
+                    }
+                    break;
+                }
+                $uid_idx++;
+            }
+        }
+
+        return $this->_pgpPacketSignature($data, $return_array);
+    }
+
+    /**
+     * Returns information on a PGP signature embedded in PGP data.  Similar
+     * to pgpPacketSignature(), but returns information by unique User ID
+     * Index (format id{n} where n is an integer of 1 or greater).
+     *
+     * @param string $pgpdata  See pgpPacketSignature().
+     * @param string $uid_idx  The UID index.
+     *
+     * @return array  See pgpPacketSignature().
+     */
+    public function pgpPacketSignatureByUidIndex($pgpdata, $uid_idx)
+    {
+        $data = $this->pgpPacketInformation($pgpdata);
+        $key_type = null;
+        $return_array = array();
+
+        /* Search for the UID index. */
+        if (!isset($data['signature']) ||
+            !isset($data['signature'][$uid_idx])) {
+            return $return_array;
+        }
+
+        /* Store the signature information now. */
+        foreach ($data['signature'][$uid_idx] as $key => $value) {
+            $return_array[$key] = $value;
+        }
+
+        return $this->_pgpPacketSignature($data, $return_array);
+    }
+
+    /**
+     * Adds some data to the pgpPacketSignature*() function array.
+     *
+     * @param array $data      See pgpPacketSignature().
+     * @param array $retarray  The return array.
+     *
+     * @return array  The return array.
+     */
+    protected function _pgpPacketSignature($data, $retarray)
+    {
+        /* If empty, return now. */
+        if (empty($retarray)) {
+            return $retarray;
+        }
+
+        $key_type = null;
+
+        /* Store any public/private key information. */
+        if (isset($data['public_key'])) {
+            $key_type = 'public_key';
+        } elseif (isset($data['secret_key'])) {
+            $key_type = 'secret_key';
+        }
+
+        if ($key_type) {
+            $retarray['key_type'] = $key_type;
+            if (isset($data[$key_type]['created'])) {
+                $retarray['key_created'] = $data[$key_type]['created'];
+            }
+            if (isset($data[$key_type]['expires'])) {
+                $retarray['key_expires'] = $data[$key_type]['expires'];
+            }
+            if (isset($data[$key_type]['size'])) {
+                $retarray['key_size'] = $data[$key_type]['size'];
+            }
+        }
+
+        return $retarray;
+    }
+
+    /**
+     * Returns the key ID of the key used to sign a block of PGP data.
+     *
+     * @param string $text  The PGP signed text block.
+     *
+     * @return string  The key ID of the key used to sign $text.
+     */
+    public function getSignersKeyID($text)
+    {
+        $keyid = null;
+
+        $input = $this->_createTempFile('horde-pgp');
+
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        $cmdline = array(
+            '--verify',
+            $input
+        );
+        $result = $this->_callGpg($cmdline, 'r', null, true, true);
+        if (preg_match('/gpg:\sSignature\smade.*ID\s+([A-F0-9]{8})\s+/', $result->stderr, $matches)) {
+            $keyid = $matches[1];
+        }
+
+        return $keyid;
+    }
+
+    /**
+     * Verify a passphrase for a given public/private keypair.
+     *
+     * @param string $public_key   The user's PGP public key.
+     * @param string $private_key  The user's PGP private key.
+     * @param string $passphrase   The user's passphrase.
+     *
+     * @return boolean  Returns true on valid passphrase, false on invalid
+     *                  passphrase, and PEAR_Error on error.
+     */
+    public function verifyPassphrase($public_key, $private_key, $passphrase)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Encrypt a test message. */
+        $result = $this->encrypt('Test', array('type' => 'message', 'pubkey' => $public_key));
+        if (is_a($result, 'PEAR_Error')) {
+            return false;
+        }
+
+        /* Try to decrypt the message. */
+        $result = $this->decrypt($result, array('type' => 'message', 'pubkey' => $public_key, 'privkey' => $private_key, 'passphrase' => $passphrase));
+        if (is_a($result, 'PEAR_Error')) {
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Parses a message into text and PGP components.
+     *
+     * @param string $text  The text to parse.
+     *
+     * @return array  An array with the parsed text, returned in blocks of
+     *                text corresponding to their actual order. Keys:
+     * <pre>
+     * 'type' -  (integer) The type of data contained in block.
+     *           Valid types are defined at the top of this class
+     *           (the ARMOR_* constants).
+     * 'data' - (array) The data for each section. Each line has been stripped
+     *          of EOL characters.
+     * </pre>
+     */
+    public function parsePGPData($text)
+    {
+        $data = array();
+        $temp_array = array(
+            'type' => self::ARMOR_TEXT
+        );
+
+        $buffer = explode("\n", $text);
+        while (list(,$val) = each($buffer)) {
+            $val = rtrim($val, "\r");
+            if (preg_match('/^-----(BEGIN|END) PGP ([^-]+)-----\s*$/', $val, $matches)) {
+                if (isset($temp_array['data'])) {
+                    $data[] = $temp_array;
+                }
+                $temp_array = array();
+
+                if ($matches[1] == 'BEGIN') {
+                    $temp_array['type'] = $this->_armor[$matches[2]];
+                    $temp_array['data'][] = $val;
+                } elseif ($matches[1] == 'END') {
+                    $temp_array['type'] = self::ARMOR_TEXT;
+                    $data[count($data) - 1]['data'][] = $val;
+                }
+            } else {
+                $temp_array['data'][] = $val;
+            }
+        }
+
+        if (isset($temp_array['data'])) {
+            $data[] = $temp_array;
+        }
+
+        return $data;
+    }
+
+    /**
+     * Returns a PGP public key from a public keyserver.
+     *
+     * @param string $keyid    The key ID of the PGP key.
+     * @param string $server   The keyserver to use.
+     * @param float $timeout   The keyserver timeout.
+     * @param string $address  The email address of the PGP key.
+     *
+     * @return string  The PGP public key, or PEAR_Error on error.
+     */
+    public function getPublicKeyserver($keyid,
+                                       $server = self::KEYSERVER_PUBLIC,
+                                       $timeout = self::KEYSERVER_TIMEOUT,
+                                       $address = null)
+    {
+        if (empty($keyid) && !empty($address)) {
+            $keyid = $this->getKeyID($address, $server, $timeout);
+            if (is_a($keyid, 'PEAR_Error')) {
+                return $keyid;
+            }
+        }
+
+        /* Connect to the public keyserver. */
+        $uri = '/pks/lookup?op=get&search=' . $this->_getKeyIDString($keyid);
+        $output = $this->_connectKeyserver('GET', $server, $uri, '', $timeout);
+        if (is_a($output, 'PEAR_Error')) {
+            return $output;
+        }
+
+        /* Strip HTML Tags from output. */
+        if (($start = strstr($output, '-----BEGIN'))) {
+            $length = strpos($start, '-----END') + 34;
+            return substr($start, 0, $length);
+        } else {
+            return PEAR::raiseError(_("Could not obtain public key from the keyserver."), 'horde.error');
+        }
+    }
+
+    /**
+     * Sends a PGP public key to a public keyserver.
+     *
+     * @param string $pubkey  The PGP public key
+     * @param string $server  The keyserver to use.
+     * @param float $timeout  The keyserver timeout.
+     *
+     * @return PEAR_Error  PEAR_Error on error/failure.
+     */
+    public function putPublicKeyserver($pubkey,
+                                       $server = self::KEYSERVER_PUBLIC,
+                                       $timeout = self::KEYSERVER_TIMEOUT)
+    {
+        /* Get the key ID of the public key. */
+        $info = $this->pgpPacketInformation($pubkey);
+
+        /* See if the public key already exists on the keyserver. */
+        if (!is_a($this->getPublicKeyserver($info['keyid'], $server, $timeout), 'PEAR_Error')) {
+            return PEAR::raiseError(_("Key already exists on the public keyserver."), 'horde.warning');
+        }
+
+        /* Connect to the public keyserver. _connectKeyserver()
+         * returns a PEAR_Error object on error and the output text on
+         * success. */
+        $pubkey = 'keytext=' . urlencode(rtrim($pubkey));
+        $cmd = array(
+            'Host: ' . $server . ':11371',
+            'User-Agent: Horde Application Framework 3.2',
+            'Content-Type: application/x-www-form-urlencoded',
+            'Content-Length: ' . strlen($pubkey),
+            'Connection: close',
+            '',
+            $pubkey
+        );
+
+        $result = $this->_connectKeyserver('POST', $server, '/pks/add', implode("\r\n", $cmd), $timeout);
+        if (is_a($result, 'PEAR_Error')) {
+            return $result;
+        }
+    }
+
+    /**
+     * Returns the first matching key ID for an email address from a
+     * public keyserver.
+     *
+     * @param string $address  The email address of the PGP key.
+     * @param string $server   The keyserver to use.
+     * @param float $timeout   The keyserver timeout.
+     *
+     * @return string  The PGP key ID, or PEAR_Error on error.
+     */
+    public function getKeyID($address, $server = self::KEYSERVER_PUBLIC,
+                      $timeout = self::KEYSERVER_TIMEOUT)
+    {
+        /* Connect to the public keyserver. */
+        $uri = '/pks/lookup?op=index&options=mr&search=' . urlencode($address);
+        $output = $this->_connectKeyserver('GET', $server, $uri, '', $timeout);
+        if (is_a($output, 'PEAR_Error')) {
+            return $output;
+        }
+
+        if (($start = strstr($output, '-----BEGIN PGP PUBLIC KEY BLOCK'))) {
+            /* The server returned the matching key immediately. */
+            $length = strpos($start, '-----END PGP PUBLIC KEY BLOCK') + 34;
+            $info = $this->pgpPacketInformation(substr($start, 0, $length));
+            if (!empty($info['keyid']) &&
+                (empty($info['public_key']['expires']) ||
+                 $info['public_key']['expires'] > time())) {
+                return $info['keyid'];
+            }
+        } elseif (strpos($output, 'pub:') !== false) {
+            $output = explode("\n", $output);
+            $keyids = array();
+            foreach ($output as $line) {
+                if (substr($line, 0, 4) == 'pub:') {
+                    $line = explode(':', $line);
+                    /* Ignore invalid lines and expired keys. */
+                    if (count($line) != 7 ||
+                        (!empty($line[5]) && $line[5] <= time())) {
+                        continue;
+                    }
+                    $keyids[$line[4]] = $line[1];
+                }
+            }
+            /* Sort by timestamp to use the newest key. */
+            if (count($keyids)) {
+                ksort($keyids);
+                return array_pop($keyids);
+            }
+        }
+
+        return PEAR::raiseError(_("Could not obtain public key from the keyserver."));
+    }
+
+    /**
+     * Get the fingerprints from a key block.
+     *
+     * @param string $pgpdata  The PGP data block.
+     *
+     * @return array The fingerprints in $pgpdata indexed by key id.
+     */
+    public function getFingerprintsFromKey($pgpdata)
+    {
+        $fingerprints = array();
+
+        /* Store the key in a temporary keyring. */
+        $keyring = $this->_putInKeyring($pgpdata);
+
+        /* Options for the GPG binary. */
+        $cmdline = array(
+            '--fingerprint',
+            $keyring,
+        );
+
+        $result = $this->_callGpg($cmdline, 'r');
+        if (!$result || !$result->stdout) {
+            return $fingerprints;
+        }
+
+        /* Parse fingerprints and key ids from output. */
+        $lines = explode("\n", $result->stdout);
+        $keyid = null;
+        foreach ($lines as $line) {
+            if (preg_match('/pub\s+\w+\/(\w{8})/', $line, $matches)) {
+                $keyid = '0x' . $matches[1];
+            } elseif ($keyid && preg_match('/^\s+[\s\w]+=\s*([\w\s]+)$/m', $line, $matches)) {
+                $fingerprints[$keyid] = trim($matches[1]);
+                $keyid = null;
+            }
+        }
+
+        return $fingerprints;
+    }
+
+    /**
+     * Connects to a public key server via HKP (Horrowitz Keyserver Protocol).
+     *
+     * @param string $method   POST, GET, etc.
+     * @param string $server   The keyserver to use.
+     * @param string $uri      The URI to access (relative to the server).
+     * @param string $command  The PGP command to run.
+     * @param float $timeout   The timeout value.
+     *
+     * @return string  The text from standard output on success, or PEAR_Error
+     *                 on error/failure.
+     */
+    protected function _connectKeyserver($method, $server, $resource,
+                                         $command, $timeout)
+    {
+        $connRefuse = 0;
+        $output = '';
+
+        $port = '11371';
+        if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) {
+            $resource = 'http://' . $server . ':' . $port . $resource;
+
+            $server = $GLOBALS['conf']['http']['proxy']['proxy_host'];
+            if (!empty($GLOBALS['conf']['http']['proxy']['proxy_port'])) {
+                $port = $GLOBALS['conf']['http']['proxy']['proxy_port'];
+            } else {
+                $port = 80;
+            }
+        }
+
+        $command = $method . ' ' . $resource . ' HTTP/1.0' . ($command ? "\r\n" . $command : '');
+
+        /* Attempt to get the key from the keyserver. */
+        do {
+            $connError = false;
+            $errno = $errstr = null;
+
+            /* The HKP server is located on port 11371. */
+            $fp = @fsockopen($server, $port, $errno, $errstr, $timeout);
+            if (!$fp) {
+                $connError = true;
+            } else {
+                fputs($fp, $command . "\n\n");
+                while (!feof($fp)) {
+                    $output .= fgets($fp, 1024);
+                }
+                fclose($fp);
+            }
+
+            if ($connError) {
+                if (++$connRefuse === self::KEYSERVER_REFUSE) {
+                    if ($errno == 0) {
+                        $output = PEAR::raiseError(_("Connection refused to the public keyserver."), 'horde.error');
+                    } else {
+                        $output = PEAR::raiseError(sprintf(_("Connection refused to the public keyserver. Reason: %s (%s)"), String::convertCharset($errstr, NLS::getExternalCharset()), $errno), 'horde.error');
+                    }
+                    break;
+                }
+            }
+        } while ($connError);
+
+        return $output;
+    }
+
+    /**
+     * Encrypts text using PGP.
+     *
+     * @param string $text   The text to be PGP encrypted.
+     * @param array $params  The parameters needed for encryption.
+     *                       See the individual _encrypt*() functions for the
+     *                       parameter requirements.
+     *
+     * @return string  The encrypted message, or PEAR_Error on error.
+     */
+    public function encrypt($text, $params = array())
+    {
+        if (isset($params['type'])) {
+            if ($params['type'] === 'message') {
+                return $this->_encryptMessage($text, $params);
+            } elseif ($params['type'] === 'signature') {
+                return $this->_encryptSignature($text, $params);
+            }
+        }
+    }
+
+    /**
+     * Decrypts text using PGP.
+     *
+     * @param string $text   The text to be PGP decrypted.
+     * @param array $params  The parameters needed for decryption.
+     *                       See the individual _decrypt*() functions for the
+     *                       parameter requirements.
+     *
+     * @return string  The decrypted message, or PEAR_Error on error.
+     */
+    public function decrypt($text, $params = array())
+    {
+        if (isset($params['type'])) {
+            if ($params['type'] === 'message') {
+                return $this->_decryptMessage($text, $params);
+            } elseif (($params['type'] === 'signature') ||
+                      ($params['type'] === 'detached-signature')) {
+                return $this->_decryptSignature($text, $params);
+            }
+        }
+    }
+
+    /**
+     * Returns whether a text has been encrypted symmetrically.
+     *
+     * @param string $text  The PGP encrypted text.
+     *
+     * @return boolean  True if the text is symmetricallly encrypted.
+     */
+    public function encryptedSymmetrically($text)
+    {
+        $cmdline = array(
+            '--decrypt',
+            '--batch'
+        );
+        $result = $this->_callGpg($cmdline, 'w', $text, true, true, true);
+        return strpos($result->stderr, 'gpg: encrypted with 1 passphrase') !== false;
+    }
+
+    /**
+     * Creates a temporary gpg keyring.
+     *
+     * @param string $type  The type of key to analyze. Either 'public'
+     *                      (Default) or 'private'
+     *
+     * @return string  Command line keystring option to use with gpg program.
+     */
+    protected function _createKeyring($type = 'public')
+    {
+        $type = String::lower($type);
+
+        if ($type === 'public') {
+            if (empty($this->_publicKeyring)) {
+                $this->_publicKeyring = $this->_createTempFile('horde-pgp');
+            }
+            return '--keyring ' . $this->_publicKeyring;
+        } elseif ($type === 'private') {
+            if (empty($this->_privateKeyring)) {
+                $this->_privateKeyring = $this->_createTempFile('horde-pgp');
+            }
+            return '--secret-keyring ' . $this->_privateKeyring;
+        }
+    }
+
+    /**
+     * Adds PGP keys to the keyring.
+     *
+     * @param mixed $keys   A single key or an array of key(s) to add to the
+     *                      keyring.
+     * @param string $type  The type of key(s) to add. Either 'public'
+     *                      (Default) or 'private'
+     *
+     * @return string  Command line keystring option to use with gpg program.
+     */
+    protected function _putInKeyring($keys = array(), $type = 'public')
+    {
+        $type = String::lower($type);
+
+        if (!is_array($keys)) {
+            $keys = array($keys);
+        }
+
+        /* Create the keyrings if they don't already exist. */
+        $keyring = $this->_createKeyring($type);
+
+        /* Store the key(s) in the keyring. */
+        $cmdline = array(
+            '--allow-secret-key-import',
+            '--fast-import',
+            $keyring
+        );
+        $this->_callGpg($cmdline, 'w', array_values($keys));
+
+        return $keyring;
+    }
+
+    /**
+     * Encrypts a message in PGP format using a public key.
+     *
+     * @param string $text   The text to be encrypted.
+     * @param array $params  The parameters needed for encryption.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'       => 'message' (REQUIRED)
+     * 'symmetric'  => Whether to use symmetric instead of asymmetric
+     *                 encryption (defaults to false)
+     * 'recips'     => An array with the e-mail address of the recipient as
+     *                 the key and that person's public key as the value.
+     *                 (REQUIRED if 'symmetric' is false)
+     * 'passphrase' => The passphrase for the symmetric encryption (REQUIRED if
+     *                 'symmetric' is true)
+     * 'pubkey'     => PGP public key. (Optional) (DEPRECATED)
+     * 'email'      => E-mail address of recipient. If not present, or not
+     *                 found in the public key, the first e-mail address found
+     *                 in the key will be used instead. (Optional) (DEPRECATED)
+     * </pre>
+     *
+     * @return string  The encrypted message, or PEAR_Error on error.
+     */
+    protected function _encryptMessage($text, $params)
+    {
+        $email = null;
+
+        if (empty($params['symmetric']) && !isset($params['recips'])) {
+            /* Check for required parameters. */
+            if (!isset($params['pubkey'])) {
+                return PEAR::raiseError(_("A public PGP key is required to encrypt a message."), 'horde.error');
+            }
+
+            /* Get information on the key. */
+            if (isset($params['email'])) {
+                $key_info = $this->pgpPacketSignature($params['pubkey'], $params['email']);
+                if (!empty($key_info)) {
+                    $email = $key_info['email'];
+                }
+            }
+
+            /* If we have no email address at this point, use the first email
+               address found in the public key. */
+            if (empty($email)) {
+                $key_info = $this->pgpPacketInformation($params['pubkey']);
+                if (isset($key_info['signature']['id1']['email'])) {
+                    $email = $key_info['signature']['id1']['email'];
+                } else {
+                    return PEAR::raiseError(_("Could not determine the recipient's e-mail address."), 'horde.error');
+                }
+            }
+
+            $params['recips'] = array($email => $params['pubkey']);
+        }
+
+        /* Create temp files for input. */
+        $input = $this->_createTempFile('horde-pgp');
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        /* Build command line. */
+        $cmdline = array(
+            '--armor',
+            '--batch',
+            '--always-trust'
+        );
+        if (empty($params['symmetric'])) {
+            /* Store public key in temporary keyring. */
+            $keyring = $this->_putInKeyring(array_values($params['recips']));
+
+            $cmdline[] = $keyring;
+            $cmdline[] = '--encrypt';
+            foreach (array_keys($params['recips']) as $val) {
+                $cmdline[] = '--recipient ' . $val;
+            }
+        } else {
+            $cmdline[] = '--symmetric';
+            $cmdline[] = '--passphrase-fd 0';
+        }
+        $cmdline[] = $input;
+
+        /* Encrypt the document. */
+        $result = $this->_callGpg($cmdline, 'w', empty($params['symmetric']) ? null : $params['passphrase'], true, true);
+        if (empty($result->output)) {
+            $error = preg_replace('/\n.*/', '', $result->stderr);
+            return PEAR::raiseError(_("Could not PGP encrypt message: ") . $error, 'horde.error');
+        }
+
+        return $result->output;
+    }
+
+    /**
+     * Signs a message in PGP format using a private key.
+     *
+     * @param string $text   The text to be signed.
+     * @param array $params  The parameters needed for signing.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'        =>  'signature' (REQUIRED)
+     * 'pubkey'      =>  PGP public key. (REQUIRED)
+     * 'privkey'     =>  PGP private key. (REQUIRED)
+     * 'passphrase'  =>  Passphrase for PGP Key. (REQUIRED)
+     * 'sigtype'     =>  Determine the signature type to use. (Optional)
+     *                   'cleartext'  --  Make a clear text signature
+     *                   'detach'     --  Make a detached signature (DEFAULT)
+     * </pre>
+     *
+     * @return string  The signed message, or PEAR_Error on error.
+     */
+    protected function _encryptSignature($text, $params)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Check for required parameters. */
+        if (!isset($params['pubkey']) ||
+            !isset($params['privkey']) ||
+            !isset($params['passphrase'])) {
+            return PEAR::raiseError(_("A public PGP key, private PGP key, and passphrase are required to sign a message."), 'horde.error');
+        }
+
+        /* Create temp files for input. */
+        $input = $this->_createTempFile('horde-pgp');
+
+        /* Encryption requires both keyrings. */
+        $pub_keyring = $this->_putInKeyring(array($params['pubkey']));
+        $sec_keyring = $this->_putInKeyring(array($params['privkey']), 'private');
+
+        /* Store message in temporary file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        /* Determine the signature type to use. */
+        $cmdline = array();
+        if (isset($params['sigtype']) &&
+            $params['sigtype'] == 'cleartext') {
+            $sign_type = '--clearsign';
+        } else {
+            $sign_type = '--detach-sign';
+        }
+
+        /* Additional GPG options. */
+        $cmdline += array(
+            '--armor',
+            '--batch',
+            '--passphrase-fd 0',
+            $sec_keyring,
+            $pub_keyring,
+            $sign_type,
+            $input
+        );
+
+        /* Sign the document. */
+        $result = $this->_callGpg($cmdline, 'w', $params['passphrase'], true, true);
+        if (empty($result->output)) {
+            $error = preg_replace('/\n.*/', '', $result->stderr);
+            return PEAR::raiseError(_("Could not PGP sign message: ") . $error, 'horde.error');
+        } else {
+            return $result->output;
+        }
+    }
+
+    /**
+     * Decrypts an PGP encrypted message using a private/public keypair and a
+     * passhprase.
+     *
+     * @param string $text   The text to be decrypted.
+     * @param array $params  The parameters needed for decryption.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'        =>  'message' (REQUIRED)
+     * 'pubkey'      =>  PGP public key. (REQUIRED for asymmetric encryption)
+     * 'privkey'     =>  PGP private key. (REQUIRED for asymmetric encryption)
+     * 'passphrase'  =>  Passphrase for PGP Key. (REQUIRED)
+     * </pre>
+     *
+     * @return stdClass  An object with the following properties, or PEAR_Error
+     *                   on error:
+     * <pre>
+     * 'message'     -  The decrypted message.
+     * 'sig_result'  -  The result of the signature test.
+     * </pre>
+     */
+    protected function _decryptMessage($text, $params)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        $good_sig_flag = false;
+
+        /* Check for required parameters. */
+        if (!isset($params['passphrase']) && empty($params['no_passphrase'])) {
+            return PEAR::raiseError(_("A passphrase is required to decrypt a message."), 'horde.error');
+        }
+
+        /* Create temp files. */
+        $input = $this->_createTempFile('horde-pgp');
+
+        /* Store message in file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        /* Build command line. */
+        $cmdline = array(
+            '--always-trust',
+            '--armor',
+            '--batch'
+        );
+        if (empty($param['no_passphrase'])) {
+            $cmdline[] = '--passphrase-fd 0';
+        }
+        if (!empty($params['pubkey']) && !empty($params['privkey'])) {
+            /* Decryption requires both keyrings. */
+            $pub_keyring = $this->_putInKeyring(array($params['pubkey']));
+            $sec_keyring = $this->_putInKeyring(array($params['privkey']), 'private');
+            $cmdline[] = $sec_keyring;
+            $cmdline[] = $pub_keyring;
+        }
+        $cmdline[] = '--decrypt';
+        $cmdline[] = $input;
+
+        /* Decrypt the document now. */
+        if (empty($params['no_passphrase'])) {
+            $result = $this->_callGpg($cmdline, 'w', $params['passphrase'], true, true);
+        } else {
+            $result = $this->_callGpg($cmdline, 'r', null, true, true);
+        }
+        if (empty($result->output)) {
+            $error = preg_replace('/\n.*/', '', $result->stderr);
+            return PEAR::raiseError(_("Could not decrypt PGP data: ") . $error, 'horde.error');
+        }
+
+        /* Create the return object. */
+        $ob = new stdClass;
+        $ob->message = $result->output;
+
+        /* Check the PGP signature. */
+        $sig_check = $this->_checkSignatureResult($result->stderr);
+        if (is_a($sig_check, 'PEAR_Error')) {
+            $ob->sig_result = $sig_check;
+        } else {
+            $ob->sig_result = ($sig_check) ? $result->stderr : '';
+        }
+
+        return $ob;
+    }
+
+    /**
+     * Decrypts an PGP signed message using a public key.
+     *
+     * @param string $text   The text to be verified.
+     * @param array $params  The parameters needed for verification.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'       =>  'signature' or 'detached-signature' (REQUIRED)
+     * 'pubkey'     =>  PGP public key. (REQUIRED)
+     * 'signature'  =>  PGP signature block. (REQUIRED for detached signature)
+     * </pre>
+     *
+     * @return string  The verification message from gpg. If no signature,
+     *                 returns empty string, and PEAR_Error on error.
+     */
+    protected function _decryptSignature($text, $params)
+    {
+        /* Check for required parameters. */
+        if (!isset($params['pubkey'])) {
+            return PEAR::raiseError(_("A public PGP key is required to verify a signed message."), 'horde.error');
+        }
+        if (($params['type'] === 'detached-signature') &&
+            !isset($params['signature'])) {
+            return PEAR::raiseError(_("The detached PGP signature block is required to verify the signed message."), 'horde.error');
+        }
+
+        $good_sig_flag = 0;
+
+        /* Create temp files for input. */
+        $input = $this->_createTempFile('horde-pgp');
+
+        /* Store public key in temporary keyring. */
+        $keyring = $this->_putInKeyring($params['pubkey']);
+
+        /* Store the message in a temporary file. */
+        $fp = fopen($input, 'w+');
+        fputs($fp, $text);
+        fclose($fp);
+
+        /* Options for the GPG binary. */
+        $cmdline = array(
+            '--armor',
+            '--always-trust',
+            '--batch',
+            '--charset ' . NLS::getCharset(),
+            $keyring,
+            '--verify'
+        );
+
+        /* Extra stuff to do if we are using a detached signature. */
+        if ($params['type'] === 'detached-signature') {
+            $sigfile = $this->_createTempFile('horde-pgp');
+            $cmdline[] = $sigfile . ' ' . $input;
+
+            $fp = fopen($sigfile, 'w+');
+            fputs($fp, $params['signature']);
+            fclose($fp);
+        } else {
+            $cmdline[] = $input;
+        }
+
+        /* Verify the signature.  We need to catch standard error output,
+         * since this is where the signature information is sent. */
+        $result = $this->_callGpg($cmdline, 'r', null, true, true);
+        $sig_result = $this->_checkSignatureResult($result->stderr);
+        if (is_a($sig_result, 'PEAR_Error')) {
+            return $sig_result;
+        } else {
+            return ($sig_result) ? $result->stderr : '';
+        }
+    }
+
+    /**
+     * Checks signature result from the GnuPG binary.
+     *
+     * @param string $result  The signature result.
+     *
+     * @return boolean  True if signature is good.
+     */
+    protected function _checkSignatureResult($result)
+    {
+        /* Good signature:
+         *   gpg: Good signature from "blah blah blah (Comment)"
+         * Bad signature:
+         *   gpg: BAD signature from "blah blah blah (Comment)" */
+        if (strpos($result, 'gpg: BAD signature') !== false) {
+            return PEAR::raiseError($result, 'horde.error');
+        } elseif (strpos($result, 'gpg: Good signature') !== false) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Signs a MIME part using PGP.
+     *
+     * @param Horde_Mime_Part $mime_part  The object to sign.
+     * @param array $params               The parameters required for signing.
+     *                                    @see _encryptSignature().
+     *
+     * @return mixed  A Horde_Mime_Part object that is signed according to RFC
+     *                3156, or PEAR_Error on error.
+     */
+    public function signMIMEPart($mime_part, $params = array())
+    {
+        $params = array_merge($params, array('type' => 'signature', 'sigtype' => 'detach'));
+
+        /* RFC 3156 Requirements for a PGP signed message:
+         * + Content-Type params 'micalg' & 'protocol' are REQUIRED.
+         * + The digitally signed message MUST be constrained to 7 bits.
+         * + The MIME headers MUST be a part of the signed data. */
+
+        $mime_part->strict7bit(true);
+        $msg_sign = $this->encrypt($mime_part->toCanonicalString(), $params);
+        if (is_a($msg_sign, 'PEAR_Error')) {
+            return $msg_sign;
+        }
+
+        /* Add the PGP signature. */
+        $charset = NLS::getEmailCharset();
+        $pgp_sign = new Horde_Mime_Part();
+        $pgp_sign->setType('application/pgp-signature');
+        $pgp_sign->setCharset($charset);
+        $pgp_sign->setDisposition('inline');
+        $pgp_sign->setDescription(String::convertCharset(_("PGP Digital Signature"), NLS::getCharset(), $charset));
+        $pgp_sign->setContents($msg_sign);
+
+        /* Get the algorithim information from the signature. Since we are
+         * analyzing a signature packet, we need to use the special keyword
+         * '_SIGNATURE' - see Horde_Crypt_pgp. */
+        $sig_info = $this->pgpPacketSignature($msg_sign, '_SIGNATURE');
+
+        /* Setup the multipart MIME Part. */
+        $part = new Horde_Mime_Part();
+        $part->setType('multipart/signed');
+        $part->setContents('This message is in MIME format and has been PGP signed.' . "\n");
+        $part->addPart($mime_part);
+        $part->addPart($pgp_sign);
+        $part->setContentTypeParameter('protocol', 'application/pgp-signature');
+        $part->setContentTypeParameter('micalg', $sig_info['micalg']);
+
+        return $part;
+    }
+
+    /**
+     * Encrypts a MIME part using PGP.
+     *
+     * @param Horde_Mime_Part $mime_part  The object to encrypt.
+     * @param array $params               The parameters required for
+     *                                    encryption.
+     *                                    @see _encryptMessage().
+     *
+     * @return mixed  A Horde_Mime_Part object that is encrypted according to
+     *                RFC 3156, or PEAR_Error on error.
+     */
+    public function encryptMIMEPart($mime_part, $params = array())
+    {
+        $params = array_merge($params, array('type' => 'message'));
+
+        $signenc_body = $mime_part->toCanonicalString();
+        $message_encrypt = $this->encrypt($signenc_body, $params);
+        if (is_a($message_encrypt, 'PEAR_Error')) {
+            return $message_encrypt;
+        }
+
+        /* Set up MIME Structure according to RFC 3156. */
+        $charset = NLS::getEmailCharset();
+        $part = new Horde_Mime_Part();
+        $part->setType('multipart/encrypted');
+        $part->setCharset($charset);
+        $part->setContentTypeParameter('protocol', 'application/pgp-encrypted');
+        $part->setDescription(String::convertCharset(_("PGP Encrypted Data"), NLS::getCharset(), $charset));
+        $part->setContents('This message is in MIME format and has been PGP encrypted.' . "\n");
+
+        $part1 = new Horde_Mime_Part();
+        $part1->setType('application/pgp-encrypted');
+        $part1->setCharset(null);
+        $part1->setContents("Version: 1\n");
+        $part->addPart($part1);
+
+        $part2 = new Horde_Mime_Part();
+        $part2->setType('application/octet-stream');
+        $part2->setCharset(null);
+        $part2->setContents($message_encrypt);
+        $part2->setDisposition('inline');
+        $part->addPart($part2);
+
+        return $part;
+    }
+
+    /**
+     * Signs and encrypts a MIME part using PGP.
+     *
+     * @param Horde_Mime_Part $mime_part   The object to sign and encrypt.
+     * @param array $sign_params           The parameters required for
+     *                                     signing. @see _encryptSignature().
+     * @param array $encrypt_params        The parameters required for
+     *                                     encryption. @see _encryptMessage().
+     *
+     * @return mixed  A Horde_Mime_Part object that is signed and encrypted
+     *                according to RFC 3156, or PEAR_Error on error.
+     */
+    public function signAndEncryptMIMEPart($mime_part, $sign_params = array(),
+                                           $encrypt_params = array())
+    {
+        /* RFC 3156 requires that the entire signed message be encrypted.  We
+         * need to explicitly call using Horde_Crypt_pgp:: because we don't
+         * know whether a subclass has extended these methods. */
+        $part = $this->signMIMEPart($mime_part, $sign_params);
+        if (is_a($part, 'PEAR_Error')) {
+            return $part;
+        }
+        $part = $this->encryptMIMEPart($part, $encrypt_params);
+        if (is_a($part, 'PEAR_Error')) {
+            return $part;
+        }
+        $part->setContents('This message is in MIME format and has been PGP signed and encrypted.' . "\n");
+
+        $charset = NLS::getEmailCharset();
+        $part->setCharset($charset);
+        $part->setDescription(String::convertCharset(_("PGP Signed/Encrypted Data"), NLS::getCharset(), $charset));
+
+        return $part;
+    }
+
+    /**
+     * Generates a Horde_Mime_Part object, in accordance with RFC 3156, that
+     * contains a public key.
+     *
+     * @param string $key  The public key.
+     *
+     * @return Horde_Mime_Part  An object that contains the public key.
+     */
+    public function publicKeyMIMEPart($key)
+    {
+        include_once 'Horde/Mime/Part.php';
+
+        $charset = NLS::getEmailCharset();
+        $part = new Horde_Mime_Part();
+        $part->setType('application/pgp-keys');
+        $part->setCharset($charset);
+        $part->setDescription(String::convertCharset(_("PGP Public Key"), NLS::getCharset(), $charset));
+        $part->setContents($key);
+
+        return $part;
+    }
+
+    /**
+     * Function that handles interfacing with the GnuPG binary.
+     *
+     * @param array $options   Options and commands to pass to GnuPG.
+     * @param string $mode     'r' to read from stdout, 'w' to write to stdin.
+     * @param array $input     Input to write to stdin.
+     * @param boolean $output  If true, collect and store output in object returned.
+     * @param boolean $stderr  If true, collect and store stderr in object returned.
+     * @param boolean $verbose If true, run GnuPG with quiet flag.
+     *
+     * @return stdClass  Class with members output, stderr, and stdout.
+     */
+    protected function _callGpg($options, $mode, $input = array(),
+                                $output = false, $stderr = false,
+                                $verbose = false)
+    {
+        $data = new stdClass;
+        $data->output = null;
+        $data->stderr = null;
+        $data->stdout = null;
+
+        /* Verbose output? */
+        if (!$verbose) {
+            array_unshift($options, '--quiet');
+        }
+
+        /* Create temp files for output. */
+        if ($output) {
+            $output_file = $this->_createTempFile('horde-pgp', false);
+            array_unshift($options, '--output ' . $output_file);
+
+            /* Do we need standard error output? */
+            if ($stderr) {
+                $stderr_file = $this->_createTempFile('horde-pgp', false);
+                $options[] = '2> ' . $stderr_file;
+            }
+        }
+
+        /* Silence errors if not requested. */
+        if (!$output || !$stderr) {
+            $options[] = '2> /dev/null';
+        }
+
+        /* Build the command line string now. */
+        $cmdline = implode(' ', array_merge($this->_gnupg, $options));
+
+        if ($mode == 'w') {
+            $fp = popen($cmdline, 'w');
+            $win32 = !strncasecmp(PHP_OS, 'WIN', 3);
+
+            if (!is_array($input)) {
+                $input = array($input);
+            }
+            foreach ($input as $line) {
+                if ($win32 && (strpos($line, "\x0d\x0a") !== false)) {
+                    $chunks = explode("\x0d\x0a", $line);
+                    foreach ($chunks as $chunk) {
+                        fputs($fp, $chunk . "\n");
+                    }
+                } else {
+                    fputs($fp, $line . "\n");
+                }
+            }
+        } elseif ($mode == 'r') {
+            $fp = popen($cmdline, 'r');
+            while (!feof($fp)) {
+                $data->stdout .= fgets($fp, 1024);
+            }
+        }
+        pclose($fp);
+
+        if ($output) {
+            $data->output = file_get_contents($output_file);
+            unlink($output_file);
+            if ($stderr) {
+                $data->stderr = file_get_contents($stderr_file);
+                unlink($stderr_file);
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * Generates a revocation certificate.
+     *
+     * @param string $key         The private key.
+     * @param string $email       The email to use for the key.
+     * @param string $passphrase  The passphrase to use for the key.
+     *
+     * @return string  The revocation certificate, or PEAR_Error on error.
+     */
+    public function generateRevocation($key, $email, $passphrase)
+    {
+        $keyring = $this->_putInKeyring($key, 'private');
+
+        /* Prepare the canned answers. */
+        $input = array();
+        $input[] = 'y'; // Really generate a revocation certificate
+        $input[] = '0'; // Refuse to specify a reason
+        $input[] = '';  // Empty comment
+        $input[] = 'y'; // Confirm empty comment
+        if (!empty($passphrase)) {
+            $input[] = $passphrase;
+        }
+
+        /* Run through gpg binary. */
+        $cmdline = array(
+            $keyring,
+            '--command-fd 0',
+            '--gen-revoke' . ' ' . $email,
+        );
+        $results = $this->_callGpg($cmdline, 'w', $input, true);
+
+        /* If the key is empty, something went wrong. */
+        if (empty($results->output)) {
+            return PEAR::raiseError(_("Revocation key not generated successfully."), 'horde.error');
+        }
+
+        return $results->output;
+    }
+
+}
diff --git a/framework/Crypt/lib/Horde/Crypt/smime.php b/framework/Crypt/lib/Horde/Crypt/smime.php
new file mode 100644 (file)
index 0000000..6e58519
--- /dev/null
@@ -0,0 +1,1366 @@
+<?php
+/**
+ * Horde_Crypt_smime:: provides a framework for Horde applications to
+ * interact with the OpenSSL library and implement S/MIME.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Mike Cochrane <mike@graftonhall.co.nz>
+ * @package Horde_Crypt
+ */
+class Horde_Crypt_smime extends Horde_Crypt
+{
+    /**
+     * Object Identifers to name array.
+     *
+     * @var array
+     */
+    protected $_oids = array(
+        '2.5.4.3' => 'CommonName',
+        '2.5.4.4' => 'Surname',
+        '2.5.4.6' => 'Country',
+        '2.5.4.7' => 'Location',
+        '2.5.4.8' => 'StateOrProvince',
+        '2.5.4.9' => 'StreetAddress',
+        '2.5.4.10' => 'Organisation',
+        '2.5.4.11' => 'OrganisationalUnit',
+        '2.5.4.12' => 'Title',
+        '2.5.4.20' => 'TelephoneNumber',
+        '2.5.4.42' => 'GivenName',
+
+        '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
+
+        '2.5.29.14' => 'id-ce-subjectKeyIdentifier',
+        '2.5.29.15' => 'id-ce-keyUsage',
+        '2.5.29.17' => 'id-ce-subjectAltName',
+        '2.5.29.19' => 'id-ce-basicConstraints',
+        '2.5.29.31' => 'id-ce-CRLDistributionPoints',
+        '2.5.29.32' => 'id-ce-certificatePolicies',
+        '2.5.29.35' => 'id-ce-authorityKeyIdentifier',
+        '2.5.29.37' => 'id-ce-extKeyUsage',
+
+        '1.2.840.113549.1.9.1' => 'Email',
+        '1.2.840.113549.1.1.1' => 'RSAEncryption',
+        '1.2.840.113549.1.1.2' => 'md2WithRSAEncryption',
+        '1.2.840.113549.1.1.4' => 'md5withRSAEncryption',
+        '1.2.840.113549.1.1.5' => 'SHA-1WithRSAEncryption',
+        '1.2.840.10040.4.3' => 'id-dsa-with-sha-1',
+
+        '1.3.6.1.5.5.7.3.2' => 'id_kp_clientAuth',
+
+        '2.16.840.1.113730.1.1' => 'netscape-cert-type',
+        '2.16.840.1.113730.1.2' => 'netscape-base-url',
+        '2.16.840.1.113730.1.3' => 'netscape-revocation-url',
+        '2.16.840.1.113730.1.4' => 'netscape-ca-revocation-url',
+        '2.16.840.1.113730.1.7' => 'netscape-cert-renewal-url',
+        '2.16.840.1.113730.1.8' => 'netscape-ca-policy-url',
+        '2.16.840.1.113730.1.12' => 'netscape-ssl-server-name',
+        '2.16.840.1.113730.1.13' => 'netscape-comment',
+    );
+
+    /**
+     * Constructor.
+     *
+     * @param array $params  Parameter array.
+     *                       'temp' => Location of temporary directory.
+     */
+    function __construct($params)
+    {
+        $this->_tempdir = $params['temp'];
+    }
+
+    /**
+     * Verify a passphrase for a given private key.
+     *
+     * @param string $private_key  The user's private key.
+     * @param string $passphrase   The user's passphrase.
+     *
+     * @return boolean  Returns true on valid passphrase, false on invalid
+     *                  passphrase.
+     *                  Returns PEAR_Error on error.
+     */
+    public function verifyPassphrase($private_key, $passphrase)
+    {
+        if (is_null($passphrase)) {
+            $res = openssl_pkey_get_private($private_key);
+        } else {
+            $res = openssl_pkey_get_private($private_key, $passphrase);
+        }
+
+        return is_resource($res);
+    }
+
+    /**
+     * Encrypt text using S/MIME.
+     *
+     * @param string $text   The text to be encrypted.
+     * @param array $params  The parameters needed for encryption.
+     *                       See the individual _encrypt*() functions for
+     *                       the parameter requirements.
+     *
+     * @return string  The encrypted message.
+     *                 Returns PEAR_Error object on error.
+     */
+    public function encrypt($text, $params = array())
+    {
+        /* Check for availability of OpenSSL PHP extension. */
+        $openssl = $this->checkForOpenSSL();
+        if (is_a($openssl, 'PEAR_Error')) {
+            return $openssl;
+        }
+
+        if (isset($params['type'])) {
+            if ($params['type'] === 'message') {
+                return $this->_encryptMessage($text, $params);
+            } elseif ($params['type'] === 'signature') {
+                return $this->_encryptSignature($text, $params);
+            }
+        }
+    }
+
+    /**
+     * Decrypt text via S/MIME.
+     *
+     * @param string $text   The text to be smime decrypted.
+     * @param array $params  The parameters needed for decryption.
+     *                       See the individual _decrypt*() functions for
+     *                       the parameter requirements.
+     *
+     * @return string  The decrypted message.
+     *                 Returns PEAR_Error object on error.
+     */
+    public function decrypt($text, $params = array())
+    {
+        /* Check for availability of OpenSSL PHP extension. */
+        $openssl = $this->checkForOpenSSL();
+        if (is_a($openssl, 'PEAR_Error')) {
+            return $openssl;
+        }
+
+        if (isset($params['type'])) {
+            if ($params['type'] === 'message') {
+                return $this->_decryptMessage($text, $params);
+            } elseif (($params['type'] === 'signature') ||
+                      ($params['type'] === 'detached-signature')) {
+                return $this->_decryptSignature($text, $params);
+            }
+        }
+    }
+
+    /**
+     * Verify a signature using via S/MIME.
+     *
+     * @param string $text  The multipart/signed data to be verified.
+     * @param mixed $certs  Either a single or array of root certificates.
+     *
+     * @return stdClass  Object with the following elements:
+     *                   'result' -> Returns true on success;
+     *                               PEAR_Error object on error.
+     *                   'cert' -> The certificate of the signer stored
+     *                             in the message (in PEM format).
+     *                   'email' -> The email of the signing person.
+     */
+    public function verify($text, $certs)
+    {
+        /* Check for availability of OpenSSL PHP extension. */
+        $openssl = $this->checkForOpenSSL();
+        if (is_a($openssl, 'PEAR_Error')) {
+            return $openssl;
+        }
+
+        /* Create temp files for input/output. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+
+        /* Write text to file */
+        file_put_contents($input, $text);
+        unset($text);
+
+        $root_certs = array();
+        if (!is_array($certs)) {
+            $certs = array($certs);
+        }
+        foreach ($certs as $file) {
+            if (file_exists($file)) {
+                $root_certs[] = $file;
+            }
+        }
+
+        $ob = new stdClass;
+
+        if (!empty($root_certs)) {
+            $result = openssl_pkcs7_verify($input, 0, $output, $root_certs);
+            /* Message verified */
+            if ($result === true) {
+                $ob->result = true;
+                $ob->cert = file_get_contents($output);
+                $ob->email = $this->getEmailFromKey($ob->cert);
+                return $ob;
+            }
+        }
+
+        /* Try again without verfying the signer's cert */
+        $result = openssl_pkcs7_verify($input, PKCS7_NOVERIFY, $output);
+
+        if ($result === true) {
+            $ob->result = PEAR::raiseError(_("Message Verified Successfully but the signer's certificate could not be verified."), 'horde.warning');
+        } elseif ($result == -1) {
+            $ob->result = PEAR::raiseError(_("Verification failed - an unknown error has occurred."), 'horde.error');
+        } else {
+            $ob->result = PEAR::raiseError(_("Verification failed - this message may have been tampered with."), 'horde.error');
+        }
+
+        $ob->cert = file_get_contents($output);
+        $ob->email = $this->getEmailFromKey($ob->cert);
+
+        return $ob;
+    }
+
+    /**
+     * Extract the contents from signed S/MIME data.
+     *
+     * @param string $data     The signed S/MIME data.
+     * @param string $sslpath  The path to the OpenSSL binary.
+     *
+     * @return string  The contents embedded in the signed data.
+     *                 Returns PEAR_Error on error.
+     */
+    public function extractSignedContents($data, $sslpath)
+    {
+        /* Check for availability of OpenSSL PHP extension. */
+        $openssl = $this->checkForOpenSSL();
+        if (is_a($openssl, 'PEAR_Error')) {
+            return $openssl;
+        }
+
+        /* Create temp files for input/output. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+
+        /* Write text to file. */
+        file_put_contents($input, $data);
+        unset($data);
+
+        exec($sslpath . ' smime -verify -noverify -nochain -in ' . $input . ' -out ' . $output);
+
+        $ret = file_get_contents($output);
+        return $ret
+            ? $ret
+            : PEAR::raiseError(_("OpenSSL error: Could not extract data from signed S/MIME part."), 'horde.error');
+    }
+
+    /**
+     * Sign a MIME part using S/MIME.
+     *
+     * @param Horde_Mime_Part $mime_part  The object to sign.
+     * @param array $params               The parameters required for signing.
+     *
+     * @return mixed  A Horde_Mime_Part object that is signed, or a
+     *                PEAR_Error object on error.
+     */
+    public function signMIMEPart($mime_part, $params)
+    {
+        require_once 'Horde/Mime/Message.php';
+
+        /* Sign the part as a message */
+        $message = $this->encrypt($mime_part->toCanonicalString(), $params);
+
+        /* Break the result into its components */
+        $mime_message = MIME_message::parseMessage($message);
+
+        $smime_sign = $mime_message->getPart('2');
+        $smime_sign->setDescription(_("S/MIME Cryptographic Signature"));
+        $smime_sign->transferDecodeContents();
+        $smime_sign->setTransferEncoding('base64');
+
+        $smime_part = new Horde_Mime_Part();
+        $smime_part->setType('multipart/signed');
+        $smime_part->setContents('This is a cryptographically signed message in MIME format.' . "\n");
+        $smime_part->addPart($mime_part);
+        $smime_part->addPart($smime_sign);
+        $smime_part->setContentTypeParameter('protocol', 'application/pkcs7-signature');
+        $smime_part->setContentTypeParameter('micalg', 'sha1');
+
+        return $smime_part;
+    }
+
+    /**
+     * Encrypt a MIME part using S/MIME.
+     *
+     * @param Horde_Mime_Part $mime_part  The object to encrypt.
+     * @param array $params               The parameters required for
+     *                                    encryption.
+     *
+     * @return mixed  A Horde_Mme_Part object that is encrypted or a
+     *                PEAR_Error on error.
+     */
+    public function encryptMIMEPart($mime_part, $params = array())
+    {
+        require_once 'Horde/Mime/Message.php';
+
+        /* Sign the part as a message */
+        $message = $this->encrypt($mime_part->toCanonicalString(), $params);
+        if (is_a($message, 'PEAR_Error')) {
+            return $message;
+        }
+
+        /* Get charset for mime part description. */
+        $charset = NLS::getEmailCharset();
+
+        /* Break the result into its components */
+        $mime_message = MIME_Message::parseMessage($message);
+
+        $mime_message->setCharset($charset);
+        $mime_message->setDescription(String::convertCharset(_("S/MIME Encrypted Message"), NLS::getCharset(), $charset));
+        $mime_message->transferDecodeContents();
+        $mime_message->setTransferEncoding('base64');
+        $mime_message->setDisposition('inline');
+
+        /* By default, encrypt() produces a message with type
+         * 'application/x-pkcs7-mime' and no 'smime-type' parameter. Per
+         * RFC 2311, the more correct MIME type is 'application/pkcs7-mime'
+         * and the smime-type should be 'enveloped-data'. */
+        $mime_message->setType('application/pkcs7-mime');
+        $mime_message->setContentTypeParameter('smime-type', 'enveloped-data');
+
+        return $mime_message;
+    }
+
+    /**
+     * Encrypt a message in S/MIME format using a public key.
+     *
+     * @param string $text   The text to be encrypted.
+     * @param array $params  The parameters needed for encryption.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'    =>  'message' (REQUIRED)
+     * 'pubkey'  =>  public key. (REQUIRED)
+     * 'email'   =>  E-mail address of recipient. If not present, or not found
+     *               in the public key, the first e-mail address found in the
+     *               key will be used instead. (Optional)
+     * </pre>
+     *
+     * @return string  The encrypted message.
+     *                 Return PEAR_Error object on error.
+     */
+    protected function _encryptMessage($text, $params)
+    {
+        $email = null;
+
+        /* Check for required parameters. */
+        if (!isset($params['pubkey'])) {
+            return PEAR::raiseError(_("A public S/MIME key is required to encrypt a message."), 'horde.error');
+        }
+
+        /* Create temp files for input/output. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+
+        /* Store message in file. */
+        file_put_contents($input, $text);
+        unset($text);
+
+        if (isset($params['email'])) {
+            $email = $params['email'];
+        } else {
+            $email = $this->getEmailFromKey($params['pubkey']);
+            if (is_null($email)) {
+                return PEAR::raiseError(_("Could not determine the recipient's e-mail address."), 'horde.error');
+            }
+        }
+
+        /* Encrypt the document. */
+        if (openssl_pkcs7_encrypt($input, $output, $params['pubkey'], array('To' => $email))) {
+            $result = file_get_contents($output);
+            if (!empty($result)) {
+                return $this->_fixContentType($result, 'encrypt');
+            }
+        }
+
+        return PEAR::raiseError(_("Could not S/MIME encrypt message."), 'horde.error');
+    }
+
+    /**
+     * Sign a message in S/MIME format using a private key.
+     *
+     * @param string $text   The text to be signed.
+     * @param array $params  The parameters needed for signing.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'certs'       =>  Additional signing certs (Optional)
+     * 'passphrase'  =>  Passphrase for key (REQUIRED)
+     * 'privkey'     =>  Private key (REQUIRED)
+     * 'pubkey'      =>  Public key (REQUIRED)
+     * 'sigtype'     =>  Determine the signature type to use. (Optional)
+     *                   'cleartext'  --  Make a clear text signature
+     *                   'detach'     --  Make a detached signature (DEFAULT)
+     * 'type'        =>  'signature' (REQUIRED)
+     * </pre>
+     *
+     * @return string  The signed message.
+     *                 Return PEAR_Error object on error.
+     */
+    protected function _encryptSignature($text, $params)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Check for required parameters. */
+        if (!isset($params['pubkey']) ||
+            !isset($params['privkey']) ||
+            !array_key_exists('passphrase', $params)) {
+            return PEAR::raiseError(_("A public S/MIME key, private S/MIME key, and passphrase are required to sign a message."), 'horde.error');
+        }
+
+        /* Create temp files for input/output/certificates. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+        $certs = $this->_createTempFile('horde-smime');
+
+        /* Store message in temporary file. */
+        file_put_contents($input, $text);
+        unset($text);
+
+        /* Store additional certs in temporary file. */
+        if (!empty($params['certs'])) {
+            file_put_contents($certs, $params['certs']);
+        }
+
+        /* Determine the signature type to use. */
+        if (isset($params['sigtype']) && ($params['sigtype'] == 'cleartext')) {
+            $flags = PKCS7_TEXT;
+        } else {
+            $flags = PKCS7_DETACHED;
+        }
+
+        $privkey = (is_null($params['passphrase'])) ? $params['privkey'] : array($params['privkey'], $params['passphrase']);
+
+        if (empty($params['certs'])) {
+            $res = openssl_pkcs7_sign($input, $output, $params['pubkey'], $privkey, array(), $flags);
+        } else {
+            $res = openssl_pkcs7_sign($input, $output, $params['pubkey'], $privkey, array(), $flags, $certs);
+        }
+
+        if (!$res) {
+            return PEAR::raiseError(_("Could not S/MIME sign message."), 'horde.error');
+        }
+
+        $data = file_get_contents($output);
+        return $this->_fixContentType($data, 'signature');
+    }
+
+    /**
+     * Decrypt an S/MIME encrypted message using a private/public keypair
+     * and a passhprase.
+     *
+     * @param string $text   The text to be decrypted.
+     * @param array $params  The parameters needed for decryption.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'type'        =>  'message' (REQUIRED)
+     * 'pubkey'      =>  public key. (REQUIRED)
+     * 'privkey'     =>  private key. (REQUIRED)
+     * 'passphrase'  =>  Passphrase for Key. (REQUIRED)
+     * </pre>
+     *
+     * @return string  The decrypted message.
+     *                 Returns PEAR_Error object on error.
+     */
+    protected function _decryptMessage($text, $params)
+    {
+        /* Check for secure connection. */
+        $secure_check = $this->requireSecureConnection();
+        if (is_a($secure_check, 'PEAR_Error')) {
+            return $secure_check;
+        }
+
+        /* Check for required parameters. */
+        if (!isset($params['pubkey']) ||
+            !isset($params['privkey']) ||
+            !array_key_exists('passphrase', $params)) {
+            return PEAR::raiseError(_("A public S/MIME key, private S/MIME key, and passphrase are required to decrypt a message."), 'horde.error');
+        }
+
+        /* Create temp files for input/output. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+
+        /* Store message in file. */
+        file_put_contents($input, $text);
+        unset($text);
+
+        $privkey = (is_null($params['passphrase'])) ? $params['privkey'] : array($params['privkey'], $params['passphrase']);
+        if (openssl_pkcs7_decrypt($input, $output, $params['pubkey'], $privkey)) {
+            return file_get_contents($output);
+        }
+
+        return PEAR::raiseError(_("Could not decrypt S/MIME data."), 'horde.error');
+    }
+
+    /**
+     * Sign and Encrypt a MIME part using S/MIME.
+     *
+     * @param Horde_Mime_Part $mime_part   The object to sign and encrypt.
+     * @param array $sign_params           The parameters required for
+     *                                     signing. @see _encryptSignature().
+     * @param array $encrypt_params        The parameters required for
+     *                                     encryption.
+     *                                     @see _encryptMessage().
+     *
+     * @return mixed  A Horde_Mime_Part object that is signed and encrypted.
+     *                Returns PEAR_Error on error.
+     */
+    public function signAndEncryptMIMEPart($mime_part, $sign_params = array(),
+                                           $encrypt_params = array())
+    {
+        $part = $this->signMIMEPart($mime_part, $sign_params);
+        if (is_a($part, 'PEAR_Error')) {
+            return $part;
+        }
+
+        return $this->encryptMIMEPart($part, $encrypt_params);
+    }
+
+    /**
+     * Convert a PEM format certificate to readable HTML version
+     *
+     * @param string $cert   PEM format certificate
+     *
+     * @return string  HTML detailing the certificate.
+     */
+    public function certToHTML($cert)
+    {
+        /* Common Fields */
+        $fieldnames = array(
+            'Email' => _("Email Address"),
+            'CommonName' => _("Common Name"),
+            'Organisation' => _("Organisation"),
+            'OrganisationalUnit' => _("Organisational Unit"),
+            'Country' => _("Country"),
+            'StateOrProvince' => _("State or Province"),
+            'Location' => _("Location"),
+            'StreetAddress' => _("Street Address"),
+            'TelephoneNumber' => _("Telephone Number"),
+            'Surname' => _("Surname"),
+            'GivenName' => _("Given Name")
+        );
+
+        /* Netscape Extensions */
+        $fieldnames += array(
+            'netscape-cert-type' => _("Netscape certificate type"),
+            'netscape-base-url' => _("Netscape Base URL"),
+            'netscape-revocation-url' => _("Netscape Revocation URL"),
+            'netscape-ca-revocation-url' => _("Netscape CA Revocation URL"),
+            'netscape-cert-renewal-url' => _("Netscape Renewal URL"),
+            'netscape-ca-policy-url' => _("Netscape CA policy URL"),
+            'netscape-ssl-server-name' => _("Netscape SSL server name"),
+            'netscape-comment' => _("Netscape certificate comment")
+        );
+
+        /* X590v3 Extensions */
+        $fieldnames += array(
+            'id-ce-extKeyUsage' => _("X509v3 Extended Key Usage"),
+            'id-ce-basicConstraints' => _("X509v3 Basic Constraints"),
+            'id-ce-subjectAltName' => _("X509v3 Subject Alternative Name"),
+            'id-ce-subjectKeyIdentifier' => _("X509v3 Subject Key Identifier"),
+            'id-ce-certificatePolicies' => _("Certificate Policies"),
+            'id-ce-CRLDistributionPoints' => _("CRL Distribution Points"),
+            'id-ce-keyUsage' => _("Key Usage")
+        );
+
+        $cert_details = $this->parseCert($cert);
+        if (!is_array($cert_details)) {
+            return '<pre class="fixed">' . _("Unable to extract certificate details") . '</pre>';
+        }
+        $certificate = $cert_details['certificate'];
+
+        $text = '<pre class="fixed">';
+
+        /* Subject (a/k/a Certificate Owner) */
+        if (isset($certificate['subject'])) {
+            $text .= "<strong>" . _("Certificate Owner") . ":</strong>\n";
+
+            foreach ($certificate['subject'] as $key => $value) {
+                if (isset($fieldnames[$key])) {
+                    $text .= sprintf("&nbsp;&nbsp;%s: %s\n", $fieldnames[$key], $value);
+                } else {
+                    $text .= sprintf("&nbsp;&nbsp;*%s: %s\n", $key, $value);
+                }
+            }
+            $text .= "\n";
+        }
+
+        /* Issuer */
+        if (isset($certificate['issuer'])) {
+            $text .= "<strong>" . _("Issuer") . ":</strong>\n";
+
+            foreach ($certificate['issuer'] as $key => $value) {
+                if (isset($fieldnames[$key])) {
+                    $text .= sprintf("&nbsp;&nbsp;%s: %s\n", $fieldnames[$key], $value);
+                } else {
+                    $text .= sprintf("&nbsp;&nbsp;*%s: %s\n", $key, $value);
+                }
+            }
+            $text .= "\n";
+        }
+
+        /* Dates  */
+        $text .= "<strong>" . _("Validity") . ":</strong>\n";
+        $text .= sprintf("&nbsp;&nbsp;%s: %s\n", _("Not Before"), strftime("%x %X", $certificate['validity']['notbefore']));
+        $text .= sprintf("&nbsp;&nbsp;%s: %s\n", _("Not After"), strftime("%x %X", $certificate['validity']['notafter']));
+        $text .= "\n";
+
+        /* Certificate Owner - Public Key Info */
+        $text .= "<strong>" . _("Public Key Info") . ":</strong>\n";
+        $text .= sprintf("&nbsp;&nbsp;%s: %s\n", _("Public Key Algorithm"), $certificate['subjectPublicKeyInfo']['algorithm']);
+        if ($certificate['subjectPublicKeyInfo']['algorithm'] == 'rsaEncryption') {
+            if (Util::extensionExists('bcmath')) {
+                $modulus = $certificate['subjectPublicKeyInfo']['subjectPublicKey']['modulus'];
+                $modulus_hex = '';
+                while ($modulus != '0') {
+                    $modulus_hex = dechex(bcmod($modulus, '16')) . $modulus_hex;
+                    $modulus = bcdiv($modulus, '16', 0);
+                }
+
+                if ((strlen($modulus_hex) > 64) &&
+                    (strlen($modulus_hex) < 128)) {
+                    str_pad($modulus_hex, 128, '0', STR_PAD_RIGHT);
+                } elseif ((strlen($modulus_hex) > 128) &&
+                          (strlen($modulus_hex) < 256)) {
+                    str_pad($modulus_hex, 256, '0', STR_PAD_RIGHT);
+                }
+
+                $text .= "&nbsp;&nbsp;" . sprintf(_("RSA Public Key (%d bit)"), strlen($modulus_hex) * 4) . ":\n";
+
+                $modulus_str = '';
+                for ($i = 0; $i < strlen($modulus_hex); $i += 2) {
+                    if (($i % 32) == 0) {
+                        $modulus_str .= "\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
+                    }
+                    $modulus_str .= substr($modulus_hex, $i, 2) . ':';
+                }
+
+                $text .= sprintf("&nbsp;&nbsp;&nbsp;&nbsp;%s: %s\n", _("Modulus"), $modulus_str);
+            }
+
+            $text .= sprintf("&nbsp;&nbsp;&nbsp;&nbsp;%s: %s\n", _("Exponent"), $certificate['subjectPublicKeyInfo']['subjectPublicKey']['publicExponent']);
+        }
+        $text .= "\n";
+
+        /* X509v3 extensions */
+        if (isset($certificate['extensions'])) {
+            $text .= "<strong>" . _("X509v3 extensions") . ":</strong>\n";
+
+            foreach ($certificate['extensions'] as $key => $value) {
+                if (is_array($value)) {
+                    $value = _("Unsupported Extension");
+                }
+                if (isset($fieldnames[$key])) {
+                    $text .= sprintf("&nbsp;&nbsp;%s:\n&nbsp;&nbsp;&nbsp;&nbsp;%s\n", $fieldnames[$key], wordwrap($value, 40, "\n&nbsp;&nbsp;&nbsp;&nbsp;"));
+                } else {
+                    $text .= sprintf("&nbsp;&nbsp;%s:\n&nbsp;&nbsp;&nbsp;&nbsp;%s\n", $key, wordwrap($value, 60, "\n&nbsp;&nbsp;&nbsp;&nbsp;"));
+                }
+            }
+
+            $text .= "\n";
+        }
+
+        /* Certificate Details */
+        $text .= "<strong>" . _("Certificate Details") . ":</strong>\n";
+        $text .= sprintf("&nbsp;&nbsp;%s: %d\n", _("Version"), $certificate['version']);
+        $text .= sprintf("&nbsp;&nbsp;%s: %d\n", _("Serial Number"), $certificate['serialNumber']);
+
+        foreach ($cert_details['fingerprints'] as $hash => $fingerprint) {
+            $label = sprintf(_("%s Fingerprint"), String::upper($hash));
+            $fingerprint_str = '';
+            for ($i = 0; $i < strlen($fingerprint); $i += 2) {
+                $fingerprint_str .= substr($fingerprint, $i, 2) . ':';
+            }
+            $text .= sprintf("&nbsp;&nbsp;%s:\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%s\n", $label, $fingerprint_str);
+        }
+        $text .= sprintf("&nbsp;&nbsp;%s: %s\n", _("Signature Algorithm"), $cert_details['signatureAlgorithm']);
+        $text .= sprintf("&nbsp;&nbsp;%s:", _("Signature"));
+
+        $sig_str = '';
+        for ($i = 0; $i < strlen($cert_details['signature']); $i++) {
+            if (($i % 16) == 0) {
+                $sig_str .= "\n&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
+            }
+            $sig_str .= sprintf("%02x:", ord($cert_details['signature'][$i]));
+        }
+
+        return $text . $sig_str . "\n</pre>";
+    }
+
+    /**
+     * Extract the contents of a PEM format certificate to an array.
+     *
+     * @param string $cert  PEM format certificate
+     *
+     * @return array  Array containing all extractable information about
+     *                the certificate.
+     */
+    public function parseCert($cert)
+    {
+        $cert_split = preg_split('/(-----((BEGIN)|(END)) CERTIFICATE-----)/', $cert);
+        if (!isset($cert_split[1])) {
+            $raw_cert = base64_decode($cert);
+        } else {
+            $raw_cert = base64_decode($cert_split[1]);
+        }
+
+        $cert_data = $this->_parseASN($raw_cert);
+        if (!is_array($cert_data) || ($cert_data[0] == 'UNKNOWN')) {
+            return false;
+        }
+
+        $cert_details = array();
+        $cert_details['fingerprints']['md5'] = md5($raw_cert);
+        $cert_details['fingerprints']['sha1'] = sha1($raw_cert);
+
+        $cert_details['certificate']['extensions'] = array();
+        $cert_details['certificate']['version'] = $cert_data[1][0][1][0][1] + 1;
+        $cert_details['certificate']['serialNumber'] = $cert_data[1][0][1][1][1];
+        $cert_details['certificate']['signature'] = $cert_data[1][0][1][2][1][0][1];
+        $cert_details['certificate']['issuer'] = $cert_data[1][0][1][3][1];
+        $cert_details['certificate']['validity'] = $cert_data[1][0][1][4][1];
+        $cert_details['certificate']['subject'] = @$cert_data[1][0][1][5][1];
+        $cert_details['certificate']['subjectPublicKeyInfo'] = $cert_data[1][0][1][6][1];
+
+        $cert_details['signatureAlgorithm'] = $cert_data[1][1][1][0][1];
+        $cert_details['signature'] = $cert_data[1][2][1];
+
+        // issuer
+        $issuer = array();
+        foreach ($cert_details['certificate']['issuer'] as $value) {
+            $issuer[$value[1][1][0][1]] = $value[1][1][1][1];
+        }
+        $cert_details['certificate']['issuer'] = $issuer;
+
+        // subject
+        $subject = array();
+        foreach ($cert_details['certificate']['subject'] as $value) {
+            $subject[$value[1][1][0][1]] = $value[1][1][1][1];
+        }
+        $cert_details['certificate']['subject'] = $subject;
+
+        // validity
+        $vals = $cert_details['certificate']['validity'];
+        $cert_details['certificate']['validity'] = array();
+        $cert_details['certificate']['validity']['notbefore'] = $vals[0][1];
+        $cert_details['certificate']['validity']['notafter'] = $vals[1][1];
+        foreach ($cert_details['certificate']['validity'] as $key => $val) {
+            $year = substr($val, 0, 2);
+            $month = substr($val, 2, 2);
+            $day = substr($val, 4, 2);
+            $hour = substr($val, 6, 2);
+            $minute = substr($val, 8, 2);
+            if (($val[11] == '-') || ($val[9] == '+')) {
+                // handle time zone offset here
+                $seconds = 0;
+            } elseif (String::upper($val[11]) == 'Z') {
+                $seconds = 0;
+            } else {
+                $seconds = substr($val, 10, 2);
+                if (($val[11] == '-') || ($val[9] == '+')) {
+                    // handle time zone offset here
+                }
+            }
+            $cert_details['certificate']['validity'][$key] = mktime ($hour, $minute, $seconds, $month, $day, $year);
+        }
+
+        // Split the Public Key into components.
+        $subjectPublicKeyInfo = array();
+        $subjectPublicKeyInfo['algorithm'] = $cert_details['certificate']['subjectPublicKeyInfo'][0][1][0][1];
+        if ($subjectPublicKeyInfo['algorithm'] == 'rsaEncryption') {
+            $subjectPublicKey = $this->_parseASN($cert_details['certificate']['subjectPublicKeyInfo'][1][1]);
+            $subjectPublicKeyInfo['subjectPublicKey']['modulus'] = $subjectPublicKey[1][0][1];
+            $subjectPublicKeyInfo['subjectPublicKey']['publicExponent'] = $subjectPublicKey[1][1][1];
+        }
+        $cert_details['certificate']['subjectPublicKeyInfo'] = $subjectPublicKeyInfo;
+
+        if (isset($cert_data[1][0][1][7]) &&
+            is_array($cert_data[1][0][1][7][1])) {
+            foreach ($cert_data[1][0][1][7][1] as $ext) {
+                $oid = $ext[1][0][1];
+                $cert_details['certificate']['extensions'][$oid] = $ext[1][1];
+            }
+        }
+
+        $i = 9;
+
+        while (isset($cert_data[1][0][1][$i]) &&
+               is_array($cert_data[1][0][1][$i][1])) {
+            $oid = $cert_data[1][0][1][$i][1][0][1];
+            $cert_details['certificate']['extensions'][$oid] = $cert_data[1][0][1][$i][1][1];
+            ++$i;
+        }
+
+        foreach ($cert_details['certificate']['extensions'] as $oid => $val) {
+            switch ($oid) {
+            case 'netscape-base-url':
+            case 'netscape-revocation-url':
+            case 'netscape-ca-revocation-url':
+            case 'netscape-cert-renewal-url':
+            case 'netscape-ca-policy-url':
+            case 'netscape-ssl-server-name':
+            case 'netscape-comment':
+                $val = $this->_parseASN($val[1]);
+                $cert_details['certificate']['extensions'][$oid] = $val[1];
+                break;
+
+            case 'id-ce-subjectAltName':
+                $val = $this->_parseASN($val[1]);
+                $cert_details['certificate']['extensions'][$oid] = '';
+                foreach ($val[1] as $name) {
+                    if (!empty($cert_details['certificate']['extensions'][$oid])) {
+                        $cert_details['certificate']['extensions'][$oid] .= ', ';
+                    }
+                    $cert_details['certificate']['extensions'][$oid] .= $name[1];
+                }
+                break;
+
+            case 'netscape-cert-type':
+                $val = $this->_parseASN($val[1]);
+                $val = ord($val[1]);
+                $newVal = '';
+
+                if ($val & 0x80) {
+                    $newVal .= empty($newVal) ? 'SSL client' : ', SSL client';
+                }
+                if ($val & 0x40) {
+                    $newVal .= empty($newVal) ? 'SSL server' : ', SSL server';
+                }
+                if ($val & 0x20) {
+                    $newVal .= empty($newVal) ? 'S/MIME' : ', S/MIME';
+                }
+                if ($val & 0x10) {
+                    $newVal .= empty($newVal) ? 'Object Signing' : ', Object Signing';
+                }
+                if ($val & 0x04) {
+                    $newVal .= empty($newVal) ? 'SSL CA' : ', SSL CA';
+                }
+                if ($val & 0x02) {
+                    $newVal .= empty($newVal) ? 'S/MIME CA' : ', S/MIME CA';
+                }
+                if ($val & 0x01) {
+                    $newVal .= empty($newVal) ? 'Object Signing CA' : ', Object Signing CA';
+                }
+
+                $cert_details['certificate']['extensions'][$oid] = $newVal;
+                break;
+
+            case 'id-ce-extKeyUsage':
+                $val = $this->_parseASN($val[1]);
+                $val = $val[1];
+
+                $newVal = '';
+                if ($val[0][1] != 'sequence') {
+                    $val = array($val);
+                } else {
+                    $val = $val[1][1];
+                }
+                foreach ($val as $usage) {
+                    if ($usage[1] == 'id_kp_clientAuth') {
+                        $newVal .= empty($newVal) ? 'TLS Web Client Authentication' : ', TLS Web Client Authentication';
+                    } else {
+                        $newVal .= empty($newVal) ? $usage[1] : ', ' . $usage[1];
+                    }
+                }
+                $cert_details['certificate']['extensions'][$oid] = $newVal;
+                break;
+
+            case 'id-ce-subjectKeyIdentifier':
+                $val = $this->_parseASN($val[1]);
+                $val = $val[1];
+
+                $newVal = '';
+
+                for ($i = 0; $i < strlen($val); $i++) {
+                    $newVal .= sprintf("%02x:", ord($val[$i]));
+                }
+                $cert_details['certificate']['extensions'][$oid] = $newVal;
+                break;
+
+            case 'id-ce-authorityKeyIdentifier':
+                $val = $this->_parseASN($val[1]);
+                if ($val[0] == 'string') {
+                    $val = $val[1];
+
+                    $newVal = '';
+                    for ($i = 0; $i < strlen($val); $i++) {
+                        $newVal .= sprintf("%02x:", ord($val[$i]));
+                    }
+                    $cert_details['certificate']['extensions'][$oid] = $newVal;
+                } else {
+                    $cert_details['certificate']['extensions'][$oid] = _("Unsupported Extension");
+                }
+                break;
+
+            case 'id-ce-basicConstraints':
+            case 'default':
+                $cert_details['certificate']['extensions'][$oid] = _("Unsupported Extension");
+                break;
+            }
+        }
+
+        return $cert_details;
+    }
+
+    /**
+     * Attempt to parse ASN.1 formated data.
+     *
+     * @param string $data  ASN.1 formated data
+     *
+     * @return array  Array contained the extracted values.
+     */
+    protected function _parseASN($data)
+    {
+        $result = array();
+
+        while (strlen($data) > 1) {
+            $class = ord($data[0]);
+            switch ($class) {
+            case 0x30:
+                // Sequence
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $sequence_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+
+                $values = $this->_parseASN($sequence_data);
+                if (!is_array($values) || is_string($values[0])) {
+                    $values = array($values);
+                }
+                $sequence_values = array();
+                $i = 0;
+                foreach ($values as $val) {
+                    if ($val[0] == 'extension') {
+                        $sequence_values['extensions'][] = $val;
+                    } else {
+                        $sequence_values[$i++] = $val;
+                    }
+                }
+                $result[] = array('sequence', $sequence_values);
+                break;
+
+            case 0x31:
+                // Set of
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $sequence_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('set', $this->_parseASN($sequence_data));
+                break;
+
+            case 0x01:
+                // Boolean type
+                $boolean_value = (ord($data[2]) == 0xff);
+                $data = substr($data, 3);
+                $result[] = array('boolean', $boolean_value);
+                break;
+
+            case 0x02:
+                // Integer type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+
+                $integer_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+
+                $value = 0;
+                if ($len <= 4) {
+                    /* Method works fine for small integers */
+                    for ($i = 0; $i < strlen($integer_data); $i++) {
+                        $value = ($value << 8) | ord($integer_data[$i]);
+                    }
+                } else {
+                    /* Method works for arbitrary length integers */
+                    if (Util::extensionExists('bcmath')) {
+                        for ($i = 0; $i < strlen($integer_data); $i++) {
+                            $value = bcadd(bcmul($value, 256), ord($integer_data[$i]));
+                        }
+                    } else {
+                        $value = -1;
+                    }
+                }
+                $result[] = array('integer(' . $len . ')', $value);
+                break;
+
+            case 0x03:
+                // Bitstring type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $bitstring_data = substr($data, 3 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('bit string', $bitstring_data);
+                break;
+
+            case 0x04:
+                // Octetstring type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $octectstring_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('octet string', $octectstring_data);
+                break;
+
+            case 0x05:
+                // Null type
+                $data = substr($data, 2);
+                $result[] = array('null', null);
+                break;
+
+            case 0x06:
+                // Object identifier type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $oid_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+
+                // Unpack the OID
+                $plain  = floor(ord($oid_data[0]) / 40);
+                $plain .= '.' . ord($oid_data[0]) % 40;
+
+                $value = 0;
+                $i = 1;
+                while ($i < strlen($oid_data)) {
+                    $value = $value << 7;
+                    $value = $value | (ord($oid_data[$i]) & 0x7f);
+
+                    if (!(ord($oid_data[$i]) & 0x80)) {
+                        $plain .= '.' . $value;
+                        $value = 0;
+                    }
+                    $i++;
+                }
+
+                if (isset($this->_oids[$plain])) {
+                    $result[] = array('oid', $this->_oids[$plain]);
+                } else {
+                    $result[] = array('oid', $plain);
+                }
+
+                break;
+
+            case 0x12:
+            case 0x13:
+            case 0x14:
+            case 0x15:
+            case 0x16:
+            case 0x81:
+            case 0x80:
+                // Character string type
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $string_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('string', $string_data);
+                break;
+
+            case 0x17:
+                // Time types
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $time_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('utctime', $time_data);
+                break;
+
+            case 0x82:
+                // X509v3 extensions?
+                $len = ord($data[1]);
+                $bytes = 0;
+                if ($len & 0x80) {
+                    $bytes = $len & 0x0f;
+                    $len = 0;
+                    for ($i = 0; $i < $bytes; $i++) {
+                        $len = ($len << 8) | ord($data[$i + 2]);
+                    }
+                }
+                $sequence_data = substr($data, 2 + $bytes, $len);
+                $data = substr($data, 2 + $bytes + $len);
+                $result[] = array('extension', 'X509v3 extensions');
+                $result[] = $this->_parseASN($sequence_data);
+                break;
+
+            case 0xa0:
+            case 0xa3:
+                // Extensions
+                $extension_data = substr($data, 0, 2);
+                $data = substr($data, 2);
+                $result[] = array('extension', dechex($extension_data));
+                break;
+
+            case 0xe6:
+                $extension_data = substr($data, 0, 1);
+                $data = substr($data, 1);
+                $result[] = array('extension', dechex($extension_data));
+                break;
+
+            case 0xa1:
+                $extension_data = substr($data, 0, 1);
+                $data = substr($data, 6);
+                $result[] = array('extension', dechex($extension_data));
+                break;
+
+            default:
+                // Unknown
+                $result[] = array('UNKNOWN', dechex($data));
+                $data = '';
+                break;
+            }
+        }
+
+        return (count($result) > 1) ? $result : array_pop($result);
+    }
+
+    /**
+     * Decrypt an S/MIME signed message using a public key.
+     *
+     * @param string $text   The text to be verified.
+     * @param array $params  The parameters needed for verification.
+     *
+     * @return string  The verification message.
+     *                 Returns PEAR_Error object on error.
+     */
+    protected function _decryptSignature($text, $params)
+    {
+        return PEAR::raiseError('_decryptSignature() ' . _("not yet implemented"));
+    }
+
+    /**
+     * Check for the presence of the OpenSSL extension to PHP.
+     *
+     * @return boolean  Returns true if the openssl extension is available.
+     *                  Returns a PEAR_Error if not.
+     */
+    public function checkForOpenSSL()
+    {
+        if (!Util::extensionExists('openssl')) {
+            return PEAR::raiseError(_("The openssl module is required for the Horde_Crypt_smime:: class."));
+        }
+    }
+
+    /**
+     * Extract the email address from a public key.
+     *
+     * @param string $key  The public key.
+     *
+     * @return mixed Returns the first email address found, or null if
+     * there are none.
+     */
+    public function getEmailFromKey($key)
+    {
+        $key_info = openssl_x509_parse($key);
+        if (!is_array($key_info)) {
+            return null;
+        }
+
+        if (isset($key_info['subject'])) {
+            if (isset($key_info['subject']['Email'])) {
+                return $key_info['subject']['Email'];
+            } elseif (isset($key_info['subject']['emailAddress'])) {
+                return $key_info['subject']['emailAddress'];
+            }
+        }
+
+        // Check subjectAltName per http://www.ietf.org/rfc/rfc3850.txt
+        if (isset($key_info['extensions']['subjectAltName'])) {
+            $names = preg_split('/\s*,\s*/', $key_info['extensions']['subjectAltName'], -1, PREG_SPLIT_NO_EMPTY);
+            foreach ($names as $name) {
+                if (strpos($name, ':') === false) {
+                    continue;
+                }
+                list($kind, $value) = explode(':', $name, 2);
+                if (String::lower($kind) == 'email') {
+                    return $value;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Convert a PKCS 12 encrypted certificate package into a private key,
+     * public key, and any additional keys.
+     *
+     * @param string $text   The PKCS 12 data.
+     * @param array $params  The parameters needed for parsing.
+     * <pre>
+     * Parameters:
+     * ===========
+     * 'sslpath' => The path to the OpenSSL binary. (REQUIRED)
+     * 'password' => The password to use to decrypt the data. (Optional)
+     * 'newpassword' => The password to use to encrypt the private key.
+     *                  (Optional)
+     * </pre>
+     *
+     * @return stdClass  An object.
+     *                   'private' -  The private key in PEM format.
+     *                   'public'  -  The public key in PEM format.
+     *                   'certs'   -  An array of additional certs.
+     *                   Returns PEAR_Error on error.
+     */
+    public function parsePKCS12Data($pkcs12, $params)
+    {
+        /* Check for availability of OpenSSL PHP extension. */
+        $openssl = $this->checkForOpenSSL();
+        if (is_a($openssl, 'PEAR_Error')) {
+            return $openssl;
+        }
+
+        if (!isset($params['sslpath'])) {
+            return PEAR::raiseError(_("No path to the OpenSSL binary provided. The OpenSSL binary is necessary to work with PKCS 12 data."), 'horde.error');
+        }
+        $sslpath = escapeshellcmd($params['sslpath']);
+
+        /* Create temp files for input/output. */
+        $input = $this->_createTempFile('horde-smime');
+        $output = $this->_createTempFile('horde-smime');
+
+        $ob = new stdClass;
+
+        /* Write text to file */
+        file_put_contents($input, $pkcs12);
+        unset($pkcs12);
+
+        /* Extract the private key from the file first. */
+        $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nocerts';
+        if (isset($params['password'])) {
+            $cmdline .= ' -passin stdin';
+            if (!empty($params['newpassword'])) {
+                $cmdline .= ' -passout stdin';
+            } else {
+                $cmdline .= ' -nodes';
+            }
+            $fd = popen($cmdline, 'w');
+            fwrite($fd, $params['password'] . "\n");
+            if (!empty($params['newpassword'])) {
+                fwrite($fd, $params['newpassword'] . "\n");
+            }
+            pclose($fd);
+        } else {
+            $cmdline .= ' -nodes';
+            exec($cmdline);
+        }
+        $ob->private = trim(file_get_contents($output));
+        if (empty($ob->private)) {
+            return PEAR::raiseError(_("Password incorrect"), 'horde.error');
+        }
+
+        /* Extract the client public key next. */
+        $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nokeys -clcerts';
+        if (isset($params['password'])) {
+            $cmdline .= ' -passin stdin';
+            $fd = popen($cmdline, 'w');
+            fwrite($fd, $params['password'] . "\n");
+            pclose($fd);
+        } else {
+            exec($cmdline);
+        }
+        $ob->public = trim(file_get_contents($output));
+
+        /* Extract the CA public key next. */
+        $cmdline = $sslpath . ' pkcs12 -in ' . $input . ' -out ' . $output . ' -nokeys -cacerts';
+        if (isset($params['password'])) {
+            $cmdline .= ' -passin stdin';
+            $fd = popen($cmdline, 'w');
+            fwrite($fd, $params['password'] . "\n");
+            pclose($fd);
+        } else {
+            exec($cmdline);
+        }
+        $ob->certs = trim(file_get_contents($output));
+
+        return $ob;
+    }
+
+    /**
+     * The Content-Type parameters PHP's openssl_pkcs7_* functions return are
+     * deprecated.  Fix these headers to the correct ones (see RFC 2311).
+     *
+     * @param string $text  The PKCS7 data.
+     * @param string $type  Is this 'message' or 'signature' data?
+     *
+     * @return string  The PKCS7 data with the correct Content-Type parameter.
+     */
+    protected function _fixContentType($text, $type)
+    {
+        if ($type == 'message') {
+            $from = 'application/x-pkcs7-mime';
+            $to = 'application/pkcs7-mime';
+        } else {
+            $from = 'application/x-pkcs7-signature';
+            $to = 'application/pkcs7-signature';
+        }
+        return str_replace('Content-Type: ' . $from, 'Content-Type: ' . $to, $text);
+    }
+
+}
diff --git a/framework/Crypt/package.xml b/framework/Crypt/package.xml
new file mode 100644 (file)
index 0000000..4e96d15
--- /dev/null
@@ -0,0 +1,139 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Horde_Crypt</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde Cryptography API</summary>
+ <description>The Horde_Crypt:: class provides an API for various cryptographic systems
+ </description>
+ <lead>
+  <name>Michael Slusarz</name>
+  <user>slusarz</user>
+  <email>slusarz@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2008-11-16</date>
+ <version>
+  <release>0.1.0</release>
+  <api>0.1.0</api>
+ </version>
+ <stability>
+  <release>alpha</release>
+  <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial Horde 4 package.</notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <dir name="Crypt">
+      <file name="pgp.php" role="php" />
+      <file name="smime.php" role="php" />
+     </dir> <!-- /lib/Horde/Crypt -->
+     <file name="Crypt.php" role="php" />
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+   <dir name="test">
+    <dir name="Horde">
+     <dir name="Crypt">
+      <dir name="fixtures">
+       <file name="clear.txt" role="test" />
+       <file name="pgp_encrypted.txt" role="test" />
+       <file name="pgp_encrypted_symmetric.txt" role="test" />
+       <file name="pgp_private.asc" role="test" />
+       <file name="pgp_public.asc" role="test" />
+       <file name="pgp_signature.txt" role="test" />
+       <file name="pgp_signed.txt" role="test" />
+       <file name="pgp_signed2.txt" role="test" />
+       <file name="smime_subjectAltName.pem" role="test" />
+      </dir> <!-- /test/Horde/Crypt/fixtures -->
+      <file name="pgp.inc" role="test" />
+      <file name="pgp_decrypt.phpt" role="test" />
+      <file name="pgp_decrypt_symmetric.phpt" role="test" />
+      <file name="pgp_encrypt.phpt" role="test" />
+      <file name="pgp_encrypt_symmetric.phpt" role="test" />
+      <file name="pgp_getSignersKeyID.phpt" role="test" />
+      <file name="pgp_parsePGPData.phpt" role="test" />
+      <file name="pgp_pgpPacketInformation.phpt" role="test" />
+      <file name="pgp_pgpPacketSignature.phpt" role="test" />
+      <file name="pgp_pgpPacketSignatureByUidIndex.phpt" role="test" />
+      <file name="pgp_pgpPrettyKey.phpt" role="test" />
+      <file name="pgp_publicKeyMIMEPart.phpt" role="test" />
+      <file name="pgp_sign.phpt" role="test" />
+      <file name="pgp_skipif.inc" role="test" />
+      <file name="pgp_verify.phpt" role="test" />
+      <file name="pgp_verifyPassphrase.phpt" role="test" />
+      <file name="smime.inc" role="test" />
+      <file name="smime_skipif.inc" role="test" />
+      <file name="smime_subjectAltName.phpt" role="test" />
+     </dir> <!-- /test/Horde/Crypt -->
+    </dir> <!-- /test/Horde -->
+   </dir> <!-- /test -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.5.0</min>
+   </pearinstaller>
+   <package>
+    <name>Horde_Mime</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Util</name>
+    <channel>pear.horde.org</channel>
+   </package>
+  </required>
+  <optional>
+   <extension>
+    <name>gettext</name>
+   </extension>
+  </optional>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Crypt/pgp.php" as="Horde/Crypt/pgp.php" />
+   <install name="lib/Horde/Crypt/smime.php" as="Horde/Crypt/smime.php" />
+   <install name="lib/Horde/Crypt.php" as="Horde/Crypt.php" />
+  </filelist>
+ </phprelease>
+ <changelog>
+  <release>
+   <version>
+    <release>0.0.2</release>
+    <api>0.0.2</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <date>2006-05-08</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>* Converted to package.xml 2.0 for pear.horde.org
+* Added support for email addresses in subjectAltName of S/MIME certs (Bug #5986)
+* Improved support for PGP fingerprints (Bug #6363)
+   </notes>
+  </release>
+  <release>
+   <version>
+    <release>0.0.1</release>
+    <api>0.0.1</api>
+   </version>
+   <stability>
+    <release>alpha</release>
+    <api>alpha</api>
+   </stability>
+   <date>2003-07-03</date>
+   <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+   <notes>Initial release as a PEAR package
+   </notes>
+  </release>
+ </changelog>
+</package>
diff --git a/framework/Crypt/test/Horde/Crypt/bug_6601.phpt b/framework/Crypt/test/Horde/Crypt/bug_6601.phpt
new file mode 100644 (file)
index 0000000..e5caba0
--- /dev/null
@@ -0,0 +1,34 @@
+--TEST--
+Bug #6601
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+// Fake preferences class to set the timezone to a consistent value.
+class mock_prefs {
+    function getValue($key)
+    {
+        return 'GMT';
+    }
+}
+$GLOBALS['prefs'] = new mock_prefs();
+
+require_once 'Horde/NLS.php';
+NLS::setTimezone();
+require 'pgp.inc';
+
+echo $pgp->pgpPrettyKey(file_get_contents(dirname(__FILE__) . '/fixtures/bug_6601.asc'));
+?>
+--EXPECT--
+Name:             Richard Selsky
+Key Type:         Public Key
+Key Creation:     04/11/08
+Expiration Date:  [Never]
+Key Length:       1024 Bytes
+Comment:          [None]
+E-Mail:           rselsky@bu.edu
+Hash-Algorithm:   pgp-sha1
+Key ID:           0xF3C01D42
+Key Fingerprint:  5912 D91D 4C79 C670 1FFF  1486 04A6 7B37 F3C0 1D42
+
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/bug_6601.asc b/framework/Crypt/test/Horde/Crypt/fixtures/bug_6601.asc
new file mode 100644 (file)
index 0000000..68e7ab2
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----\r
+Version: GnuPG v1.4.9 (MingW32)\r
+\r
+mQGiBEf/ossRBACchuYgSW0Uz8DzoLTWAjx3leau0jdNq9ud4HzO9lzstkWh14k3\r
+Ld+KN9p4Z168NNj7fLtUKzwxLTTjDS9cMiwq5/gpP28ta0gyu8Ooq1i5U1BlwzKT\r
+UFRduQ3jQKK2ZXa2jMeXdWcchYwMGj5qd/1YlM2MpGnKfOTYyZozs22OpwCg+6qR\r
+mHAAcrY1QUN4zSlUlFIxAzUD+gPY0RQU0Ke1UE48pve6QcOlnRNFnj5XLjEzn2M8\r
+kkjbYBG2mEtXHALpYIQa7KrtfoD3nGLjf4wqzHrwYlWEu17jIdodCGI1F8dX7Mm+\r
+ARO8DbiX/fk1H53jGjrf4h4OEc6H6B3dMEKEO0S7ZNNp6QgudCjd+LHNJ0pjyzow\r
+y84KA/9OXCkvZh/3xXBOPkO5LLmAw0ZZEEP7Sna75kw02G7S2elhRqWrLzVNzv2O\r
+ppaj3oz73XYGxZAjsPOdmWYajCnycO7BqK7VFGw1EySldUmWB/Uadpeb8PwLpHFJ\r
+d+mZy2INSHEAn484FHXHYSUYPOyNypGejwY2IOI0e/MjKqTHLbQfUmljaGFyZCBT\r
+ZWxza3kgPHJzZWxza3lAYnUuZWR1PohmBBMRAgAmBQJH/6LLAhsjBQkJZgGABgsJ\r
+CAcDAgQVAggDBBYCAwECHgECF4AACgkQBKZ7N/PAHUK25QCglppEGY1V0pK/hmuO\r
+ocxXd23mJt0AnRIpLvWpVMjXfr/TYbrDXCaNv62FuQINBEf/ossQCADozRNDx7HC\r
+KFHcX/ZZjW24Yc+GAHwxLq2Vtq/VyrlxCDFfw2tGFcKMa21f1WFCk2Z60GxiMAhq\r
+2yE1ATkkAKHE0r9huz/NkuBqJXwbZzWV/9VkrDArGqTZDXdydpF0fADsqe64LPBU\r
+b3kGI5UmJHkiyLD60EsldPM+sWe7VATRHjnikbu72J1JbLtvUxGoUB8LrwWcHIJr\r
+Sql21XGUev8kabNhOOOUcj0uojvPlbmdj1aK9QuqcQ/fe2wK56s3LYM+u5mDb+uY\r
+p+9pwAAfW2yqjx/gQrQZxXk0DZrmSHJzrBm6wvzWOwCXE8MM5S+Bp7xObjT0Hjiu\r
+dZzuI5Q2MuB7AAMGCACvmsTPNwPKYDENZYujNSWWeES3byyLpOzSNt1ehed44nXT\r
+ur5738BwCfHtdsqBVXZUS6k/2BabgxdDyBSzBn3utKCos7LFxNy4GmUNqU8cZgJj\r
+gwA+poyTAJW4ndOlQbLGTOCOyaiXi1gKpv3RJQB1ADXNAazP29I+LWSWNOBeL1/p\r
+EevcD1mCMM3tmoWOlBBm0HDg3UZtHg56D6cm5BVYjRODJNdOI8IDOwj4kONjIOS3\r
++kfWgeJZmo8JatxhS2jBIGb5W033hI3a3EtUYrIRxsFAKT4QT/9YEDzZnIqxQIq8\r
+Q5AE5Ij6OjAKFO8bR1f7RiQsjgZK96GABBLgWsr6iE8EGBECAA8FAkf/ossCGwwF\r
+CQlmAYAACgkQBKZ7N/PAHUKkFQCgnfx8fwXdCMrFJqV5ukOnzOr/q7YAniA3CIgF\r
+dAM6dnvvMMNWckjddcwg\r
+=ynmU\r
+-----END PGP PUBLIC KEY BLOCK-----\r
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/clear.txt b/framework/Crypt/test/Horde/Crypt/fixtures/clear.txt
new file mode 100644 (file)
index 0000000..9d32ba6
--- /dev/null
@@ -0,0 +1,14 @@
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted.txt
new file mode 100644 (file)
index 0000000..985f410
--- /dev/null
@@ -0,0 +1,15 @@
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+hQEOAyTtKXee8HSpEAP6Aj5+9VlYxyMQepJxeejXHePK58xs6jSv1oUn/I4zjdW3
+XKvXAGy/2dQdYLOFHCfMqzLx2cKPh/UL5+M89p/oSIXpeWz2anxrt/1JxnQHSIaD
+n88jQ/H6Es07wGbJ41RvM8n4aobdVXVk9ujLi1nk4/2Pi2Ped22I6aAi3eO+dRED
+/An4L6SCmFFdqTphEjIHRSeqf6QVp/tjdnYXMkMrqVJGtFSsvfrbhF7RGyWVQra3
+rEZdnJaPPF6CwtG99akP38I6iQ0c9GqWX8/VfAYaQyeqKkDJpdzfN8Pe9Z8Q9XVs
+/qQXaIM2QxSen7K8qEC5WBah/ZOWpSrWSlk+fM65AXaS0qUBwvQoHi2/tjijfGZD
+uKt4cYNal3aZLeDNWry+nGO2mckjCYsOiyerPOYPU84OvygndcOR8Y8r8+/No8IA
+vCzvSVygJ0MCvq0QtIXRF8XDVQJt5eSAo18Tb4yfc6cU/NKWApY0/92OPAiRu3sA
+NdxJM6Whb1cDsfAIOPT9sX4yM2K7JmnoxP8owOtu/ViwvIkWiJ55+dPNUVFG3cAf
+KUjSRnwgJrg=
+=RtGk
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted_symmetric.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_encrypted_symmetric.txt
new file mode 100644 (file)
index 0000000..5d17066
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+jA0EAwMC8w6TJuJ4zhBgyYdWhfV8CJIOWg2IHcVVngMH5xHp7eiESg753YuwYaCJ
+bYR4pItctfQbRDVhlQXqveO6SZCv9WJVrtiUBIo0N4r3p2Hsx0gL34MgLW02xpTc
+4CfPwB+3VNeSTym5unWvVsB4VAuW78Xvc8fw6tjZnTPNUqA2IfH30gkpfnzBwcno
+lNMhxacLMXE=
+=N1Bk
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_private.asc b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_private.asc
new file mode 100644 (file)
index 0000000..a11a979
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+lQHhBETcWvARBADNitbvsWy5/hhV+WcU2ttmtXkAj2DqJVgJdGS2RH8msO0roG5j
+CQK/e0iMJki5lfdgxvxxWgStYMnfF5ftgWA7JV+BZUzJt12Lobm0zdENv2TqL2vc
+xlPTmEGsvfPDTbY+Gr3jvuODboXat7bUn2E723WXPdh2A7KNNnLou7JF2wCgiKs/
+RqNKM/Zm01PxLbQ+rs9ghd0D/jLUfJeYWySoDsvfO8e4UyDxDVTBLkkdw3XzLx1F
+4SS/Cc2Z9yJuXiepzSH/G/vhdN5ROv12kJwA4FbwsFv5C1uCQleWiPngFixca9Nw
+lAd2X2Cp0/4D2XRq1M9dEbcYdrgAuyzt2ZToj3aFaYNGwjfHoLqSngOu6/d3KD1d
+i0b2A/9wnXo41kPwS73gU1Un2KKMkKqnczCQHdpopO6NjKaLhNcouRauLrgbrS5y
+A1CW+nxjkKVvWrP/VFBmapUpjE1C51J9P0/ub8tRr7H0xHdTQyufv01lmfkjUpVF
+n3GVf95l4seBFzD7r580aTx+dJztoHEGWrsWZTNJwo6IIlFOIf4DAwKhFBeOUwqs
+hWA4x/9ffd4UC9JengF978ktHEm4RtB+5QS2angOVMG4mHqkeQobo2qEta2lciQQ
+aqeJ5rQlTXkgTmFtZSAoTXkgQ29tbWVudCkgPG1lQGV4YW1wbGUuY29tPohgBBMR
+AgAgBQJE3FrwAhsjBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQfKdEJrreq9fi
+vACeLBcWErSQU4ZGQsjedhwfdst9cLQAnRETpwmqt/XvcGFVsOE28MUrUzOGnQFX
+BETcWvAQBADNgIJ4nDlfgBOI/iqyNy08C9+MjxrqMylrj4TPn3rRk8wySr2myX+j
+9CML5EHOtsxANYeI9u7hOF11n5Z8fDht/WGJrNR7EtRhNN6mkKsPaEuO3fhz6Egw
+JYReUVzDJbvnV2fRCvQoEGaSntZGQaQzIzIL+/gMEFpEVRK1P2I3VwADBQP+K2Rm
+mkm3DonXFlUUDWWdhEF4b7fy5/IPj41PSSOdo0IP4dprFoe15Vs9gWOYvzcnjy+B
+bOwhVwsjE3F36hf04od3uTSM0dLS/xmpSvgbBh181T5c3W5aKT/daVhyxXJ4csxE
++JCVKbaBubne0DPEuyZjrYlL5Lm0z3VhNCcR0Lz+AwMCoRQXjlMKrIVgzwaI65oM
+T4XCFhFGk67RoJ4D1I4R/7pXZffFhdCHsPzZBHqsadyC0chmu2ZoYtIcOxxSQmzc
+4swO7XC5lohJBBgRAgAJBQJE3FrwAhsMAAoJEHynRCa63qvXpjcAoIeCpIKzYgIj
+AfGSLg/OfLBCU3xMAJ9V3CCBw+tP458WDRprIE+SbhUY8Q==
+=41gN
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_public.asc b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_public.asc
new file mode 100644 (file)
index 0000000..98b3585
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+mQGiBETcWvARBADNitbvsWy5/hhV+WcU2ttmtXkAj2DqJVgJdGS2RH8msO0roG5j
+CQK/e0iMJki5lfdgxvxxWgStYMnfF5ftgWA7JV+BZUzJt12Lobm0zdENv2TqL2vc
+xlPTmEGsvfPDTbY+Gr3jvuODboXat7bUn2E723WXPdh2A7KNNnLou7JF2wCgiKs/
+RqNKM/Zm01PxLbQ+rs9ghd0D/jLUfJeYWySoDsvfO8e4UyDxDVTBLkkdw3XzLx1F
+4SS/Cc2Z9yJuXiepzSH/G/vhdN5ROv12kJwA4FbwsFv5C1uCQleWiPngFixca9Nw
+lAd2X2Cp0/4D2XRq1M9dEbcYdrgAuyzt2ZToj3aFaYNGwjfHoLqSngOu6/d3KD1d
+i0b2A/9wnXo41kPwS73gU1Un2KKMkKqnczCQHdpopO6NjKaLhNcouRauLrgbrS5y
+A1CW+nxjkKVvWrP/VFBmapUpjE1C51J9P0/ub8tRr7H0xHdTQyufv01lmfkjUpVF
+n3GVf95l4seBFzD7r580aTx+dJztoHEGWrsWZTNJwo6IIlFOIbQlTXkgTmFtZSAo
+TXkgQ29tbWVudCkgPG1lQGV4YW1wbGUuY29tPohgBBMRAgAgBQJE3FrwAhsjBgsJ
+CAcDAgQVAggDBBYCAwECHgECF4AACgkQfKdEJrreq9fivACeLBcWErSQU4ZGQsje
+dhwfdst9cLQAnRETpwmqt/XvcGFVsOE28MUrUzOGuQENBETcWvAQBADNgIJ4nDlf
+gBOI/iqyNy08C9+MjxrqMylrj4TPn3rRk8wySr2myX+j9CML5EHOtsxANYeI9u7h
+OF11n5Z8fDht/WGJrNR7EtRhNN6mkKsPaEuO3fhz6EgwJYReUVzDJbvnV2fRCvQo
+EGaSntZGQaQzIzIL+/gMEFpEVRK1P2I3VwADBQP+K2Rmmkm3DonXFlUUDWWdhEF4
+b7fy5/IPj41PSSOdo0IP4dprFoe15Vs9gWOYvzcnjy+BbOwhVwsjE3F36hf04od3
+uTSM0dLS/xmpSvgbBh181T5c3W5aKT/daVhyxXJ4csxE+JCVKbaBubne0DPEuyZj
+rYlL5Lm0z3VhNCcR0LyISQQYEQIACQUCRNxa8AIbDAAKCRB8p0Qmut6r16Y3AJ9h
+umO5uT5yDcir3zwqUAxzBAkE4ACcCtGfb6usaTKnNXo+ZuLoHiOwIE4=
+=GCjU
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signature.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signature.txt
new file mode 100644 (file)
index 0000000..7e0f2d8
--- /dev/null
@@ -0,0 +1,7 @@
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+iD8DBQBE3JdufKdEJrreq9cRAhQ2AJ44XNI1uKRJB2KR3EvhUCcIKQwJ0wCfRsjv
+5pAbY1qypr+8m0ZHJJcpubE=
+=F0tK
+-----END PGP SIGNATURE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed.txt
new file mode 100644 (file)
index 0000000..0cb1968
--- /dev/null
@@ -0,0 +1,24 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+iD8DBQFE3JNgfKdEJrreq9cRAm4lAJ48IbiwbO4ToXa2BrJaAZAFt43AiACZATs+
+gnfrwrK41BzMWmVRhtjB1Po=
+=5HXb
+-----END PGP SIGNATURE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed2.txt b/framework/Crypt/test/Horde/Crypt/fixtures/pgp_signed2.txt
new file mode 100644 (file)
index 0000000..746d676
--- /dev/null
@@ -0,0 +1,9 @@
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v1.4.5 (GNU/Linux)
+
+owGbwMvMwCRYs9xFbde91dcZT79MYnC5M0XBwNDI2MTUzNzCEj+LKyQjVaGwNDM5
+WyGpKL88TyEtv0IhqzS3oFghvyy1SKEEKJ2TWFWpkJKfrjeqmD6KFSgxeRoVHU10
+KlJUUlmuqqahaWsft2VDgrayrp6Olnq8lbWNXU1dTHV0bC1Xhx0zKzhlwpKqIFPg
+ZIYZfd+39c/3v2Hbbi9tZT5P+o/7eyOGuVJ6rUZsLHVikwvyfuc/2lPb3Cv/CwA=
+=Cw7w
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/fixtures/smime_subjectAltName.pem b/framework/Crypt/test/Horde/Crypt/fixtures/smime_subjectAltName.pem
new file mode 100644 (file)
index 0000000..ba80372
--- /dev/null
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGzCCAoSgAwIBAgIJAO0Hyim1pgtmMA0GCSqGSIb3DQEBBQUAMFIxCzAJBgNV
+BAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNodXNldHRzMQ8wDQYDVQQHEwZCb3N0b24x
+GjAYBgNVBAoTEVRoZSBIb3JkZSBQcm9qZWN0MB4XDTA3MTIyODIyMDQwN1oXDTA4
+MDEyNzIyMDQwN1owUjELMAkGA1UEBhMCVVMxFjAUBgNVBAgTDU1hc3NhY2h1c2V0
+dHMxDzANBgNVBAcTBkJvc3RvbjEaMBgGA1UEChMRVGhlIEhvcmRlIFByb2plY3Qw
+gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOKFM64qxeVqHmfxxhBzE/zCgqzA
+8c5JxJc2YYfKPCkQWGozRfUq45YDV4CsfJNgGMbjYdOe2cQOMpofdWgobD1xnKid
+mmwRvxTuqjOhjx020VFOMuq0t8tl7dYPrsQcNl+9g7Pg2mG2oC768NQHgP6GnZgH
+LPxkfEaDDVrqjSyFAgMBAAGjgfgwgfUwQQYDVR0RBDowOIERdGVzdDFAZXhhbXBs
+ZS5jb22BEXRlc3QyQGV4YW1wbGUuY29tgRB0ZXN0QGV4YW1wbGUuY29tMB0GA1Ud
+DgQWBBReeRq4wDuMdSnGMChBJCSzXmGxTTCBggYDVR0jBHsweYAUXnkauMA7jHUp
+xjAoQSQks15hsU2hVqRUMFIxCzAJBgNVBAYTAlVTMRYwFAYDVQQIEw1NYXNzYWNo
+dXNldHRzMQ8wDQYDVQQHEwZCb3N0b24xGjAYBgNVBAoTEVRoZSBIb3JkZSBQcm9q
+ZWN0ggkA7QfKKbWmC2YwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQDN
+ZR6muF28V3EdsUAwIw7JuZSGMnmZJdKz+b/aNGbK40iZY29OGmEnhy3qBFQHTQY3
+jSpLszDH3l9ZSLNjhoOmnm1TydCMAW4/7dQny7ONx/CFS3r9vHxBNreNk+ZmJgDJ
+ASZwyX6IszJ1a3k4t+N3KY9+ZzSISIHJTPZZPzJiRA==
+-----END CERTIFICATE-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp.inc b/framework/Crypt/test/Horde/Crypt/pgp.inc
new file mode 100644 (file)
index 0000000..ea02f31
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+/**
+ * @package Horde_Crypt
+ */
+
+$filedir = dirname(__FILE__);
+
+require 'PEAR.php';
+require 'Horde/Util.php';
+require 'Horde/Browser.php';
+require $filedir . '/../../../lib/Horde/Crypt.php';
+
+$_SERVER['HTTPS'] = 'on';
+$browser = &Browser::singleton();
+
+$pgp = Horde_Crypt::factory('pgp', array(
+    'program' => '/usr/bin/gpg',
+    'temp' => Util::getTempDir()
+));
+
+$pubkey = file_get_contents($filedir . '/fixtures/pgp_public.asc');
+$privkey = file_get_contents($filedir . '/fixtures/pgp_private.asc');
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_decrypt.phpt b/framework/Crypt/test/Horde/Crypt/pgp_decrypt.phpt
new file mode 100644 (file)
index 0000000..b6c94fa
--- /dev/null
@@ -0,0 +1,37 @@
+--TEST--
+Horde_Crypt_pgp::decrypt() message.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$crypt = file_get_contents(dirname(__FILE__) . '/fixtures/pgp_encrypted.txt');
+
+$decrypt = $pgp->decrypt($crypt,
+                         array('type' => 'message',
+                               'pubkey' => $pubkey,
+                               'privkey' => $privkey,
+                               'passphrase' => 'Secret'));
+if (is_a($decrypt, 'PEAR_Error')) {
+    var_dump($decrypt);
+} else {
+    echo $decrypt->message;
+}
+
+?>
+--EXPECT--
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_decrypt_symmetric.phpt b/framework/Crypt/test/Horde/Crypt/pgp_decrypt_symmetric.phpt
new file mode 100644 (file)
index 0000000..5204835
--- /dev/null
@@ -0,0 +1,35 @@
+--TEST--
+Horde_Crypt_pgp::decrypt() message.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$crypt = file_get_contents(dirname(__FILE__) . '/fixtures/pgp_encrypted_symmetric.txt');
+
+$decrypt = $pgp->decrypt($crypt,
+                         array('type' => 'message',
+                               'passphrase' => 'Secret'));
+if (is_a($decrypt, 'PEAR_Error')) {
+    var_dump($decrypt);
+} else {
+    echo $decrypt->message;
+}
+
+?>
+--EXPECT--
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_encrypt.phpt b/framework/Crypt/test/Horde/Crypt/pgp_encrypt.phpt
new file mode 100644 (file)
index 0000000..b7ad787
--- /dev/null
@@ -0,0 +1,31 @@
+--TEST--
+Horde_Crypt_pgp::encrypt() message.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$clear = file_get_contents(dirname(__FILE__) . '/fixtures/clear.txt');
+
+echo $pgp->encrypt($clear,
+                   array('type' => 'message',
+                         'recips' => array('me@example.com' => $pubkey)));
+
+?>
+--EXPECTF--
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v%d.%d.%d (%s)
+
+%s
+%s
+%s
+%s
+%s
+%s
+%s
+%s
+%s
+%s
+=%s
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_encrypt_symmetric.phpt b/framework/Crypt/test/Horde/Crypt/pgp_encrypt_symmetric.phpt
new file mode 100644 (file)
index 0000000..745e82b
--- /dev/null
@@ -0,0 +1,26 @@
+--TEST--
+Horde_Crypt_pgp::encrypt() message symmetric.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$clear = file_get_contents(dirname(__FILE__) . '/fixtures/clear.txt');
+
+echo $pgp->encrypt($clear,
+                   array('type' => 'message',
+                         'symmetric' => true,
+                         'passphrase' => 'Secret'));
+
+?>
+--EXPECTF--
+-----BEGIN PGP MESSAGE-----
+Version: GnuPG v%d.%d.%d (%s)
+
+%s
+%s
+%s
+%s
+=%s
+-----END PGP MESSAGE-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_encryptedSymmetrically.phpt b/framework/Crypt/test/Horde/Crypt/pgp_encryptedSymmetrically.phpt
new file mode 100644 (file)
index 0000000..0dbb939
--- /dev/null
@@ -0,0 +1,15 @@
+--TEST--
+Horde_Crypt_pgp::encryptedSymmetrically()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+var_dump($pgp->encryptedSymmetrically(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_encrypted.txt')));
+var_dump($pgp->encryptedSymmetrically(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_encrypted_symmetric.txt')));
+
+?>
+--EXPECT--
+bool(false)
+bool(true)
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_generateRevocation.phpt b/framework/Crypt/test/Horde/Crypt/pgp_generateRevocation.phpt
new file mode 100644 (file)
index 0000000..ba94391
--- /dev/null
@@ -0,0 +1,20 @@
+--TEST--
+Horde_Crypt_pgp::generateRevocation()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+echo $pgp->generateRevocation($privkey, 'me@example.com', 'Secret');
+
+?>
+--EXPECTF--
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v%d.%d.%d (%s)
+Comment: A revocation certificate should follow
+
+%s
+%s
+=%s
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_getSignersKeyID.phpt b/framework/Crypt/test/Horde/Crypt/pgp_getSignersKeyID.phpt
new file mode 100644 (file)
index 0000000..98fa8e3
--- /dev/null
@@ -0,0 +1,13 @@
+--TEST--
+Horde_Crypt_pgp::getSignersKeyID()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+echo $pgp->getSignersKeyID(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signed.txt'));
+
+?>
+--EXPECT--
+BADEABD7
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_parsePGPData.phpt b/framework/Crypt/test/Horde/Crypt/pgp_parsePGPData.phpt
new file mode 100644 (file)
index 0000000..48accc6
--- /dev/null
@@ -0,0 +1,89 @@
+--TEST--
+Horde_Crypt_pgp::parsePGPData().
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+
+var_dump($pgp->parsePGPData(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signed.txt')));
+
+?>
+--EXPECT--
+array(3) {
+  [0]=>
+  array(2) {
+    ["type"]=>
+    int(2)
+    ["data"]=>
+    array(17) {
+      [0]=>
+      string(34) "-----BEGIN PGP SIGNED MESSAGE-----"
+      [1]=>
+      string(10) "Hash: SHA1"
+      [2]=>
+      string(0) ""
+      [3]=>
+      string(40) "0123456789012345678901234567890123456789"
+      [4]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [5]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [6]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [7]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [8]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [9]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [10]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [11]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [12]=>
+      string(89) "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog."
+      [13]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [14]=>
+      string(44) "The quick brown fox jumps over the lazy dog."
+      [15]=>
+      string(40) "0123456789012345678901234567890123456789"
+      [16]=>
+      string(33) "!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}"
+    }
+  }
+  [1]=>
+  array(2) {
+    ["type"]=>
+    int(5)
+    ["data"]=>
+    array(7) {
+      [0]=>
+      string(29) "-----BEGIN PGP SIGNATURE-----"
+      [1]=>
+      string(33) "Version: GnuPG v1.4.5 (GNU/Linux)"
+      [2]=>
+      string(0) ""
+      [3]=>
+      string(64) "iD8DBQFE3JNgfKdEJrreq9cRAm4lAJ48IbiwbO4ToXa2BrJaAZAFt43AiACZATs+"
+      [4]=>
+      string(24) "gnfrwrK41BzMWmVRhtjB1Po="
+      [5]=>
+      string(5) "=5HXb"
+      [6]=>
+      string(27) "-----END PGP SIGNATURE-----"
+    }
+  }
+  [2]=>
+  array(2) {
+    ["type"]=>
+    int(6)
+    ["data"]=>
+    array(1) {
+      [0]=>
+      string(0) ""
+    }
+  }
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketInformation.phpt b/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketInformation.phpt
new file mode 100644 (file)
index 0000000..7eb8ee6
--- /dev/null
@@ -0,0 +1,133 @@
+--TEST--
+Horde_Crypt_pgp::pgpPacketInformation()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+var_dump($pgp->pgpPacketInformation($pubkey));
+var_dump($pgp->pgpPacketInformation($privkey));
+
+?>
+--EXPECT--
+array(4) {
+  ["public_key"]=>
+  array(3) {
+    ["created"]=>
+    string(10) "1155291888"
+    ["expires"]=>
+    string(1) "0"
+    ["size"]=>
+    string(4) "1024"
+  }
+  ["signature"]=>
+  array(2) {
+    ["id1"]=>
+    array(7) {
+      ["name"]=>
+      string(7) "My Name"
+      ["comment"]=>
+      string(10) "My Comment"
+      ["email"]=>
+      string(14) "me@example.com"
+      ["keyid"]=>
+      string(16) "7CA74426BADEABD7"
+      ["fingerprint"]=>
+      string(16) "7CA74426BADEABD7"
+      ["sig_7CA74426BADEABD7"]=>
+      array(4) {
+        ["keyid"]=>
+        string(16) "7CA74426BADEABD7"
+        ["fingerprint"]=>
+        string(16) "7CA74426BADEABD7"
+        ["created"]=>
+        string(10) "1155291888"
+        ["micalg"]=>
+        string(8) "pgp-sha1"
+      }
+      ["micalg"]=>
+      string(8) "pgp-sha1"
+    }
+    ["_SIGNATURE"]=>
+    array(2) {
+      ["micalg"]=>
+      string(8) "pgp-sha1"
+      ["sig_7CA74426BADEABD7"]=>
+      array(4) {
+        ["keyid"]=>
+        string(16) "7CA74426BADEABD7"
+        ["fingerprint"]=>
+        string(16) "7CA74426BADEABD7"
+        ["created"]=>
+        string(10) "1155291888"
+        ["micalg"]=>
+        string(8) "pgp-sha1"
+      }
+    }
+  }
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+}
+array(4) {
+  ["secret_key"]=>
+  array(3) {
+    ["created"]=>
+    string(10) "1155291888"
+    ["expires"]=>
+    string(1) "0"
+    ["size"]=>
+    string(4) "1024"
+  }
+  ["signature"]=>
+  array(2) {
+    ["id1"]=>
+    array(7) {
+      ["name"]=>
+      string(7) "My Name"
+      ["comment"]=>
+      string(10) "My Comment"
+      ["email"]=>
+      string(14) "me@example.com"
+      ["keyid"]=>
+      string(16) "7CA74426BADEABD7"
+      ["fingerprint"]=>
+      string(16) "7CA74426BADEABD7"
+      ["sig_7CA74426BADEABD7"]=>
+      array(4) {
+        ["keyid"]=>
+        string(16) "7CA74426BADEABD7"
+        ["fingerprint"]=>
+        string(16) "7CA74426BADEABD7"
+        ["created"]=>
+        string(10) "1155291888"
+        ["micalg"]=>
+        string(8) "pgp-sha1"
+      }
+      ["micalg"]=>
+      string(8) "pgp-sha1"
+    }
+    ["_SIGNATURE"]=>
+    array(2) {
+      ["micalg"]=>
+      string(8) "pgp-sha1"
+      ["sig_7CA74426BADEABD7"]=>
+      array(4) {
+        ["keyid"]=>
+        string(16) "7CA74426BADEABD7"
+        ["fingerprint"]=>
+        string(16) "7CA74426BADEABD7"
+        ["created"]=>
+        string(10) "1155291888"
+        ["micalg"]=>
+        string(8) "pgp-sha1"
+      }
+    }
+  }
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignature.phpt b/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignature.phpt
new file mode 100644 (file)
index 0000000..4c45345
--- /dev/null
@@ -0,0 +1,82 @@
+--TEST--
+Horde_Crypt_pgp::pgpPacketSignature()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+var_dump($pgp->pgpPacketSignature($pubkey, 'me@example.com'));
+var_dump($pgp->pgpPacketSignature($privkey, 'me@example.com'));
+var_dump($pgp->pgpPacketSignature($privkey, 'foo@example.com'));
+
+?>
+--EXPECT--
+array(11) {
+  ["name"]=>
+  string(7) "My Name"
+  ["comment"]=>
+  string(10) "My Comment"
+  ["email"]=>
+  string(14) "me@example.com"
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+  ["sig_7CA74426BADEABD7"]=>
+  array(4) {
+    ["keyid"]=>
+    string(16) "7CA74426BADEABD7"
+    ["fingerprint"]=>
+    string(16) "7CA74426BADEABD7"
+    ["created"]=>
+    string(10) "1155291888"
+    ["micalg"]=>
+    string(8) "pgp-sha1"
+  }
+  ["micalg"]=>
+  string(8) "pgp-sha1"
+  ["key_type"]=>
+  string(10) "public_key"
+  ["key_created"]=>
+  string(10) "1155291888"
+  ["key_expires"]=>
+  string(1) "0"
+  ["key_size"]=>
+  string(4) "1024"
+}
+array(11) {
+  ["name"]=>
+  string(7) "My Name"
+  ["comment"]=>
+  string(10) "My Comment"
+  ["email"]=>
+  string(14) "me@example.com"
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+  ["sig_7CA74426BADEABD7"]=>
+  array(4) {
+    ["keyid"]=>
+    string(16) "7CA74426BADEABD7"
+    ["fingerprint"]=>
+    string(16) "7CA74426BADEABD7"
+    ["created"]=>
+    string(10) "1155291888"
+    ["micalg"]=>
+    string(8) "pgp-sha1"
+  }
+  ["micalg"]=>
+  string(8) "pgp-sha1"
+  ["key_type"]=>
+  string(10) "secret_key"
+  ["key_created"]=>
+  string(10) "1155291888"
+  ["key_expires"]=>
+  string(1) "0"
+  ["key_size"]=>
+  string(4) "1024"
+}
+array(0) {
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignatureByUidIndex.phpt b/framework/Crypt/test/Horde/Crypt/pgp_pgpPacketSignatureByUidIndex.phpt
new file mode 100644 (file)
index 0000000..92597b5
--- /dev/null
@@ -0,0 +1,82 @@
+--TEST--
+Horde_Crypt_pgp::pgpPacketSignatureByUidIndex()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+var_dump($pgp->pgpPacketSignatureByUidIndex($pubkey, 'id1'));
+var_dump($pgp->pgpPacketSignatureByUidIndex($privkey, 'id1'));
+var_dump($pgp->pgpPacketSignatureByUidIndex($privkey, 'id2'));
+
+?>
+--EXPECT--
+array(11) {
+  ["name"]=>
+  string(7) "My Name"
+  ["comment"]=>
+  string(10) "My Comment"
+  ["email"]=>
+  string(14) "me@example.com"
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+  ["sig_7CA74426BADEABD7"]=>
+  array(4) {
+    ["keyid"]=>
+    string(16) "7CA74426BADEABD7"
+    ["fingerprint"]=>
+    string(16) "7CA74426BADEABD7"
+    ["created"]=>
+    string(10) "1155291888"
+    ["micalg"]=>
+    string(8) "pgp-sha1"
+  }
+  ["micalg"]=>
+  string(8) "pgp-sha1"
+  ["key_type"]=>
+  string(10) "public_key"
+  ["key_created"]=>
+  string(10) "1155291888"
+  ["key_expires"]=>
+  string(1) "0"
+  ["key_size"]=>
+  string(4) "1024"
+}
+array(11) {
+  ["name"]=>
+  string(7) "My Name"
+  ["comment"]=>
+  string(10) "My Comment"
+  ["email"]=>
+  string(14) "me@example.com"
+  ["keyid"]=>
+  string(16) "7CA74426BADEABD7"
+  ["fingerprint"]=>
+  string(16) "7CA74426BADEABD7"
+  ["sig_7CA74426BADEABD7"]=>
+  array(4) {
+    ["keyid"]=>
+    string(16) "7CA74426BADEABD7"
+    ["fingerprint"]=>
+    string(16) "7CA74426BADEABD7"
+    ["created"]=>
+    string(10) "1155291888"
+    ["micalg"]=>
+    string(8) "pgp-sha1"
+  }
+  ["micalg"]=>
+  string(8) "pgp-sha1"
+  ["key_type"]=>
+  string(10) "secret_key"
+  ["key_created"]=>
+  string(10) "1155291888"
+  ["key_expires"]=>
+  string(1) "0"
+  ["key_size"]=>
+  string(4) "1024"
+}
+array(0) {
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_pgpPrettyKey.phpt b/framework/Crypt/test/Horde/Crypt/pgp_pgpPrettyKey.phpt
new file mode 100644 (file)
index 0000000..bbb9d56
--- /dev/null
@@ -0,0 +1,34 @@
+--TEST--
+Horde_Crypt_pgp::pgpPrettyKey()
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+echo $pgp->pgpPrettyKey($pubkey);
+echo $pgp->pgpPrettyKey($privkey);
+
+?>
+--EXPECT--
+Name:             My Name
+Key Type:         Public Key
+Key Creation:     08/11/06
+Expiration Date:  [Never]
+Key Length:       1024 Bytes
+Comment:          My Comment
+E-Mail:           me@example.com
+Hash-Algorithm:   pgp-sha1
+Key ID:           0xBADEABD7
+Key Fingerprint:  966F 4BA9 569D E6F6 5E82  5397 7CA7 4426 BADE ABD7
+
+Name:             My Name
+Key Type:         Private Key
+Key Creation:     08/11/06
+Expiration Date:  [Never]
+Key Length:       1024 Bytes
+Comment:          My Comment
+E-Mail:           me@example.com
+Hash-Algorithm:   pgp-sha1
+Key ID:           0xBADEABD7
+Key Fingerprint:  966F 4BA9 569D E6F6 5E82  5397 7CA7 4426 BADE ABD7
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_publicKeyMIMEPart.phpt b/framework/Crypt/test/Horde/Crypt/pgp_publicKeyMIMEPart.phpt
new file mode 100644 (file)
index 0000000..886a656
--- /dev/null
@@ -0,0 +1,53 @@
+--TEST--
+Horde_Crypt_pgp::publicKeyMIMEPart().
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+class PrefsStub {
+    function getValue($pref)
+    {
+        if ($pref == 'sending_charset') {
+            return 'iso-8859-1';
+        }
+        die('unknown preference');
+    }
+}
+
+require_once 'Horde/NLS.php';
+require 'pgp.inc';
+
+$prefs = new PrefsStub;
+$mime_part = $pgp->publicKeyMIMEPart($pubkey);
+echo $mime_part->getType() . "\n\n";
+echo $mime_part->getContents();
+
+?>
+--EXPECTF--
+application/pgp-keys
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v%d.%d.%d (GNU/Linux)
+
+mQGiBETcWvARBADNitbvsWy5/hhV+WcU2ttmtXkAj2DqJVgJdGS2RH8msO0roG5j
+CQK/e0iMJki5lfdgxvxxWgStYMnfF5ftgWA7JV+BZUzJt12Lobm0zdENv2TqL2vc
+xlPTmEGsvfPDTbY+Gr3jvuODboXat7bUn2E723WXPdh2A7KNNnLou7JF2wCgiKs/
+RqNKM/Zm01PxLbQ+rs9ghd0D/jLUfJeYWySoDsvfO8e4UyDxDVTBLkkdw3XzLx1F
+4SS/Cc2Z9yJuXiepzSH/G/vhdN5ROv12kJwA4FbwsFv5C1uCQleWiPngFixca9Nw
+lAd2X2Cp0/4D2XRq1M9dEbcYdrgAuyzt2ZToj3aFaYNGwjfHoLqSngOu6/d3KD1d
+i0b2A/9wnXo41kPwS73gU1Un2KKMkKqnczCQHdpopO6NjKaLhNcouRauLrgbrS5y
+A1CW+nxjkKVvWrP/VFBmapUpjE1C51J9P0/ub8tRr7H0xHdTQyufv01lmfkjUpVF
+n3GVf95l4seBFzD7r580aTx+dJztoHEGWrsWZTNJwo6IIlFOIbQlTXkgTmFtZSAo
+TXkgQ29tbWVudCkgPG1lQGV4YW1wbGUuY29tPohgBBMRAgAgBQJE3FrwAhsjBgsJ
+CAcDAgQVAggDBBYCAwECHgECF4AACgkQfKdEJrreq9fivACeLBcWErSQU4ZGQsje
+dhwfdst9cLQAnRETpwmqt/XvcGFVsOE28MUrUzOGuQENBETcWvAQBADNgIJ4nDlf
+gBOI/iqyNy08C9+MjxrqMylrj4TPn3rRk8wySr2myX+j9CML5EHOtsxANYeI9u7h
+OF11n5Z8fDht/WGJrNR7EtRhNN6mkKsPaEuO3fhz6EgwJYReUVzDJbvnV2fRCvQo
+EGaSntZGQaQzIzIL+/gMEFpEVRK1P2I3VwADBQP+K2Rmmkm3DonXFlUUDWWdhEF4
+b7fy5/IPj41PSSOdo0IP4dprFoe15Vs9gWOYvzcnjy+BbOwhVwsjE3F36hf04od3
+uTSM0dLS/xmpSvgbBh181T5c3W5aKT/daVhyxXJ4csxE+JCVKbaBubne0DPEuyZj
+rYlL5Lm0z3VhNCcR0LyISQQYEQIACQUCRNxa8AIbDAAKCRB8p0Qmut6r16Y3AJ9h
+umO5uT5yDcir3zwqUAxzBAkE4ACcCtGfb6usaTKnNXo+ZuLoHiOwIE4=
+=GCjU
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_sign.phpt b/framework/Crypt/test/Horde/Crypt/pgp_sign.phpt
new file mode 100644 (file)
index 0000000..5cc91c0
--- /dev/null
@@ -0,0 +1,55 @@
+--TEST--
+Horde_Crypt_pgp::encrypt() signature.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+$clear = file_get_contents(dirname(__FILE__) . '/fixtures/clear.txt');
+
+echo $pgp->encrypt($clear,
+                   array('type' => 'signature',
+                         'pubkey' => $pubkey,
+                         'privkey' => $privkey,
+                         'passphrase' => 'Secret'));
+echo $pgp->encrypt($clear,
+                   array('type' => 'signature',
+                         'pubkey' => $pubkey,
+                         'privkey' => $privkey,
+                         'passphrase' => 'Secret',
+                         'sigtype' => 'cleartext'));
+
+?>
+--EXPECTF--
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v%d.%d.%d (%s)
+
+%s
+%s
+=%s
+-----END PGP SIGNATURE-----
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA1
+
+0123456789012345678901234567890123456789
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+The quick brown fox jumps over the lazy dog.
+0123456789012345678901234567890123456789
+!"$§%&()=?^´°`+#-.,*'_:;<>|~\{[]}
+-----BEGIN PGP SIGNATURE-----
+Version: GnuPG v%d.%d.%d (%s)
+
+%s
+%s
+=%s
+-----END PGP SIGNATURE-----
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_skipif.inc b/framework/Crypt/test/Horde/Crypt/pgp_skipif.inc
new file mode 100644 (file)
index 0000000..53e230f
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+/**
+ * @package Horde_Crypt
+ */
+
+if (!is_executable('/usr/bin/gpg')) {
+    echo 'skip No GPG binary at /usr/bin/gpg';
+}
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_verify.phpt b/framework/Crypt/test/Horde/Crypt/pgp_verify.phpt
new file mode 100644 (file)
index 0000000..0d4f960
--- /dev/null
@@ -0,0 +1,41 @@
+--TEST--
+Horde_Crypt_pgp::decrypt() signature.
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+// Fake preferences class to set the timezone to a consistent value.
+class mock_prefs {
+    function getValue($key)
+    {
+        return 'GMT';
+    }
+}
+$GLOBALS['prefs'] = new mock_prefs();
+
+require_once 'Horde/NLS.php';
+NLS::setTimezone();
+require 'pgp.inc';
+
+echo $pgp->decrypt(file_get_contents(dirname(__FILE__) . '/fixtures/clear.txt'),
+                   array('type' => 'detached-signature',
+                         'pubkey' => $pubkey,
+                         'signature' => file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signature.txt')));
+
+echo $pgp->decrypt(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signed.txt'),
+                   array('type' => 'signature',
+                         'pubkey' => $pubkey));
+
+echo $pgp->decrypt(file_get_contents(dirname(__FILE__) . '/fixtures/pgp_signed2.txt'),
+                   array('type' => 'signature',
+                         'pubkey' => $pubkey));
+
+?>
+--EXPECT--
+gpg: Signature made Fri Aug 11 14:42:54 2006 GMT using DSA key ID BADEABD7
+gpg: Good signature from "My Name (My Comment) <me@example.com>"
+gpg: Signature made Fri Aug 11 14:25:36 2006 GMT using DSA key ID BADEABD7
+gpg: Good signature from "My Name (My Comment) <me@example.com>"
+gpg: Signature made Fri Aug 11 14:28:48 2006 GMT using DSA key ID BADEABD7
+gpg: Good signature from "My Name (My Comment) <me@example.com>"
\ No newline at end of file
diff --git a/framework/Crypt/test/Horde/Crypt/pgp_verifyPassphrase.phpt b/framework/Crypt/test/Horde/Crypt/pgp_verifyPassphrase.phpt
new file mode 100644 (file)
index 0000000..33c5e94
--- /dev/null
@@ -0,0 +1,16 @@
+--TEST--
+Horde_Crypt_pgp::verifyPassphrase().
+--SKIPIF--
+<?php require 'pgp_skipif.inc'; ?>
+--FILE--
+<?php
+
+require 'pgp.inc';
+
+var_dump($pgp->verifyPassphrase($pubkey, $privkey, 'Secret'));
+var_dump($pgp->verifyPassphrase($pubkey, $privkey, 'Wrong'));
+
+?>
+--EXPECT--
+bool(true)
+bool(false)
diff --git a/framework/Crypt/test/Horde/Crypt/smime.inc b/framework/Crypt/test/Horde/Crypt/smime.inc
new file mode 100644 (file)
index 0000000..fab7acd
--- /dev/null
@@ -0,0 +1,11 @@
+<?php
+/**
+ * @package Horde_Crypt
+ */
+
+require 'PEAR.php';
+require 'Horde/String.php';
+require 'Horde/Util.php';
+require dirname(__FILE__) . '/../../../lib/Horde/Crypt.php';
+
+$smime = Horde_Crypt::factory('smime', array('temp' => Util::getTempDir()));
diff --git a/framework/Crypt/test/Horde/Crypt/smime_skipif.inc b/framework/Crypt/test/Horde/Crypt/smime_skipif.inc
new file mode 100644 (file)
index 0000000..2dd7baf
--- /dev/null
@@ -0,0 +1,8 @@
+<?php
+/**
+ * @package Horde_Crypt
+ */
+
+if (!extension_loaded('openssl')) {
+    echo 'skip No openssl support in PHP';
+}
diff --git a/framework/Crypt/test/Horde/Crypt/smime_subjectAltName.phpt b/framework/Crypt/test/Horde/Crypt/smime_subjectAltName.phpt
new file mode 100644 (file)
index 0000000..adc4d1e
--- /dev/null
@@ -0,0 +1,17 @@
+--TEST--
+Horde_Crypt_smime::getEmailFromKey() with subjectAltName
+--SKIPIF--
+<?php
+echo "skip openssl_x509_parse() doesn't return subjectAltName of the example cert.";
+require 'smime_skipif.inc';
+?>
+--FILE--
+<?php
+
+require 'smime.inc';
+$key = file_get_contents(dirname(__FILE__) . '/fixtures/smime_subjectAltName.pem');
+echo $smime->getEmailFromKey($key);
+
+?>
+--EXPECT--
+test1@example.com
diff --git a/framework/Mime/lib/Horde/Mime.php b/framework/Mime/lib/Horde/Mime.php
new file mode 100644 (file)
index 0000000..834daa0
--- /dev/null
@@ -0,0 +1,559 @@
+<?php
+/**
+ * The Horde_Mime:: class provides methods for dealing with various MIME (see,
+ * e.g., RFC 2045-2049; 2183; 2231) standards.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Michael Slusarz <slusarz@curecanti.org>
+ * @package Horde_Mime
+ */
+class Horde_Mime
+{
+    /**
+     * Attempt to work around non RFC 2231-compliant MUAs by generating both
+     * a RFC 2047-like parameter name and  also the correct RFC 2231
+     * parameter.  See:
+     * http://lists.horde.org/archives/dev/Week-of-Mon-20040426/014240.html
+     *
+     * @var boolean
+     */
+    static public $brokenRFC2231 = false;
+
+    /**
+     * Determines if a string contains 8-bit (non US-ASCII) characters.
+     *
+     * @param string $string   The string to check.
+     * @param string $charset  The charset of the string. Defaults to
+     *                         US-ASCII.
+     *
+     * @return boolean  True if string contains non US-ASCII characters.
+     */
+    static public function is8bit($string, $charset = null)
+    {
+        /* ISO-2022-JP is a 7bit charset, but it is an 8bit representation so
+         * it needs to be entirely encoded. */
+        return is_string($string) &&
+               ((stristr('iso-2022-jp', $charset) &&
+                (strstr($string, "\x1b\$B"))) ||
+                preg_match('/[\x80-\xff]/', $string));
+    }
+
+    /**
+     * Encodes a string containing non-ASCII characters according to RFC 2047.
+     *
+     * @param string $text     The text to encode.
+     * @param string $charset  The character set of the text.
+     *
+     * @return string  The text, encoded only if it contains non-ASCII
+     *                 characters.
+     */
+    static public function encode($text, $charset = null)
+    {
+        if (is_null($charset)) {
+            require_once 'Horde/NLS.php';
+            $charset = NLS::getCharset();
+        }
+        $charset = String::lower($charset);
+
+        if (($charset == 'us-ascii') || !self::is8bit($text, $charset)) {
+            return $text;
+        }
+
+        /* Get the list of elements in the string. */
+        $size = preg_match_all('/([^\s]+)([\s]*)/', $text, $matches, PREG_SET_ORDER);
+
+        $line = '';
+
+        /* Return if nothing needs to be encoded. */
+        foreach ($matches as $key => $val) {
+            if (self::is8bit($val[1], $charset)) {
+                if ((($key + 1) < $size) &&
+                    self::is8bit($matches[$key + 1][1], $charset)) {
+                    $line .= self::_encode($val[1] . $val[2], $charset) . ' ';
+                } else {
+                    $line .= self::_encode($val[1], $charset) . $val[2];
+                }
+            } else {
+                $line .= $val[1] . $val[2];
+            }
+        }
+
+        return rtrim($line);
+    }
+
+    /**
+     * Internal recursive function to RFC 2047 encode a string.
+     *
+     * @param string $text     The text to encode.
+     * @param string $charset  The character set of the text.
+     *
+     * @return string  The text, encoded only if it contains non-ASCII
+     *                 characters.
+     */
+    static protected function _encode($text, $charset)
+    {
+        $encoded = trim(base64_encode($text));
+        $c_size = strlen($charset) + 7;
+
+        if ((strlen($encoded) + $c_size) > 75) {
+            $parts = explode("\r\n", rtrim(chunk_split($encoded, intval((75 - $c_size) / 4) * 4)));
+        } else {
+            $parts[] = $encoded;
+        }
+
+        $p_size = count($parts);
+        $out = '';
+
+        foreach ($parts as $key => $val) {
+            $out .= '=?' . $charset . '?b?' . $val . '?=';
+            if ($p_size > $key + 1) {
+                /* RFC 2047 [2]: no encoded word can be more than 75
+                 * characters long. If longer, you must split the word with
+                 * CRLF SPACE. */
+                $out .= "\r\n ";
+            }
+        }
+
+        return $out;
+    }
+
+    /**
+     * Encodes a line via quoted-printable encoding.
+     *
+     * @param string $text   The text to encode.
+     * @param string $eol    The EOL sequence to use.
+     * @param integer $wrap  Wrap a line at this many characters.
+     *
+     * @return string  The quoted-printable encoded string.
+     */
+    static public function quotedPrintableEncode($text, $eol, $wrap = 76)
+    {
+        $line = $output = '';
+        $curr_length = 0;
+
+        /* We need to go character by character through the data. */
+        for ($i = 0, $length = strlen($text); $i < $length; ++$i) {
+            $char = $text[$i];
+
+            /* If we have reached the end of the line, reset counters. */
+            if ($char == "\n") {
+                $output .= $eol;
+                $curr_length = 0;
+                continue;
+            } elseif ($char == "\r") {
+                continue;
+            }
+
+            /* Spaces or tabs at the end of the line are NOT allowed. Also,
+             * ASCII characters below 32 or above 126 AND 61 must be
+             * encoded. */
+            $ascii = ord($char);
+            if ((($ascii === 32) &&
+                 ($i + 1 != $length) &&
+                 (($text[$i + 1] == "\n") || ($text[$i + 1] == "\r"))) ||
+                (($ascii < 32) || ($ascii > 126) || ($ascii === 61))) {
+                $char_len = 3;
+                $char = '=' . String::upper(sprintf('%02s', dechex($ascii)));
+            } else {
+                $char_len = 1;
+            }
+
+            /* Lines must be $wrap characters or less. */
+            $curr_length += $char_len;
+            if ($curr_length > $wrap) {
+                $output .= '=' . $eol;
+                $curr_length = $char_len;
+            }
+            $output .= $char;
+        }
+
+        return $output;
+    }
+
+    /**
+     * Encodes a string containing email addresses according to RFC 2047.
+     *
+     * This differs from encode() because it keeps email addresses legal, only
+     * encoding the personal information.
+     *
+     * @param mixed $addresses   The email addresses to encode (either a
+     *                           string or an array of addresses).
+     * @param string $charset    The character set of the text.
+     * @param string $defserver  The default domain to append to mailboxes.
+     *
+     * @return string  The text, encoded only if it contains non-ASCII
+     *                 characters, or PEAR_Error on error.
+     */
+    static public function encodeAddress($addresses, $charset = null,
+                                         $defserver = null)
+    {
+        if (!is_array($addresses)) {
+            /* parseAddressList() does not process the null entry
+             * 'undisclosed-recipients:;' correctly. */
+            $addresses = trim($addresses);
+            if (preg_match('/undisclosed-recipients:\s*;/i', $addresses)) {
+                return $addresses;
+            }
+
+            $addresses = Horde_Mime_Address::parseAddressList($addresses, array('defserver' => $defserver, 'nestgroups' => true));
+            if (is_a($addresses, 'PEAR_Error')) {
+                return $addresses;
+            }
+        }
+
+        $text = '';
+        foreach ($addresses as $addr) {
+            // Check for groups.
+            if (empty($addr['groupname'])) {
+                if (empty($addr['personal'])) {
+                    $personal = '';
+                } else {
+                    if (($addr['personal'][0] == '"') &&
+                        (substr($addr['personal'], -1) == '"')) {
+                        $addr['personal'] = stripslashes(substr($addr['personal'], 1, -1));
+                    }
+                    $personal = self::encode($addr['personal'], $charset);
+                }
+                $text .= Horde_Mime_Address::writeAddress($addr['mailbox'], $addr['host'], $personal) . ', ';
+            } else {
+                $text .= Horde_Mime_Address::writeGroupAddress($addr['groupname'], $addr['addresses']) . ' ';
+            }
+        }
+
+        return rtrim($text, ' ,');
+    }
+
+    /**
+     * Decodes an RFC 2047-encoded string.
+     *
+     * @param string $string      The text to decode.
+     * @param string $to_charset  The charset that the text should be decoded
+     *                            to.
+     *
+     * @return string  The decoded text.
+     */
+    static public function decode($string, $to_charset = null)
+    {
+        if (($pos = strpos($string, '=?')) === false) {
+            return $string;
+        }
+
+        /* Take out any spaces between multiple encoded words. */
+        $string = preg_replace('|\?=\s+=\?|', '?==?', $string);
+
+        /* Save any preceding text. */
+        $preceding = substr($string, 0, $pos);
+
+        $search = substr($string, $pos + 2);
+        $d1 = strpos($search, '?');
+        if ($d1 === false) {
+            return $string;
+        }
+
+        $charset = substr($string, $pos + 2, $d1);
+        $search = substr($search, $d1 + 1);
+
+        $d2 = strpos($search, '?');
+        if ($d2 === false) {
+            return $string;
+        }
+
+        $encoding = substr($search, 0, $d2);
+        $search = substr($search, $d2 + 1);
+
+        $end = strpos($search, '?=');
+        if ($end === false) {
+            $end = strlen($search);
+        }
+
+        $encoded_text = substr($search, 0, $end);
+        $rest = substr($string, (strlen($preceding . $charset . $encoding . $encoded_text) + 6));
+
+        if (is_null($to_charset)) {
+            require_once 'Horde/NLS.php';
+            $to_charset = NLS::getCharset();
+        }
+
+        switch ($encoding) {
+        case 'Q':
+        case 'q':
+            $decoded = preg_replace('/=([0-9a-f]{2})/ie', 'chr(0x\1)', str_replace('_', ' ', $encoded_text));
+            $decoded = String::convertCharset($decoded, $charset, $to_charset);
+            break;
+
+        case 'B':
+        case 'b':
+            $decoded = String::convertCharset(base64_decode($encoded_text), $charset, $to_charset);
+            break;
+
+        default:
+            $decoded = '=?' . $charset . '?' . $encoding . '?' . $encoded_text . '?=';
+            break;
+        }
+
+        return $preceding . $decoded . self::decode($rest, $to_charset);
+    }
+
+    /**
+     * Decodes an RFC 2047-encoded address string.
+     *
+     * @param string $string      The text to decode.
+     * @param string $to_charset  The charset that the text should be decoded
+     *                            to.
+     *
+     * @return string  The decoded text.
+     */
+    static public function decodeAddrString($string, $to_charset = null)
+    {
+        $addr_list = array();
+        foreach (Horde_Mime_Address::parseAddressList($string) as $ob) {
+            $ob['personal'] = isset($ob['personal'])
+                ? self::decode($ob['personal'], $to_charset)
+                : '';
+            $addr_list[] = $ob;
+        }
+
+        return Horde_Mime_Address::addrArray2String($addr_list);
+    }
+
+    /**
+     * Encodes a MIME parameter string pursuant to RFC 2183 & 2231
+     * (Content-Type and Content-Disposition headers).
+     *
+     * @param string $name     The parameter name.
+     * @param string $val      The parameter value.
+     * @param string $charset  The charset the text should be encoded with.
+     * @param string $lang     The language to use when encoding.
+     *
+     * @return array  The encoded parameter string.
+     */
+    static public function encodeParam($name, $val, $charset, $lang = null)
+    {
+        $encode = $wrap = false;
+        $output = array();
+        $curr = 0;
+
+        // 2 = '=', ';'
+        $pre_len = strlen($name) + 2;
+
+        if (self::is8bit($val, $charset)) {
+            $string = String::lower($charset) . '\'' . (is_null($lang) ? '' : String::lower($lang)) . '\'' . rawurlencode($val);
+            $encode = true;
+            /* Account for trailing '*'. */
+            ++$pre_len;
+        } else {
+            $string = $val;
+        }
+
+        if (($pre_len + strlen($string)) > 75) {
+            /* Account for continuation '*'. */
+            ++$pre_len;
+            $wrap = true;
+
+            while ($string) {
+                $chunk = 75 - $pre_len - strlen($curr);
+                $pos = min($chunk, strlen($string) - 1);
+
+                /* Don't split in the middle of an encoded char. */
+                if (($chunk == $pos) && ($pos > 2)) {
+                    for ($i = 0; $i <= 2; ++$i) {
+                        if ($string[$pos - $i] == '%') {
+                            $pos -= $i + 1;
+                            break;
+                        }
+                    }
+                }
+
+                $lines[] = substr($string, 0, $pos + 1);
+                $string = substr($string, $pos + 1);
+                ++$curr;
+            }
+        } else {
+            $lines = array($string);
+        }
+
+        foreach ($lines as $i => $line) {
+            $output[$name . (($wrap) ? ('*' . $i) : '') . (($encode) ? '*' : '')] = $line;
+        }
+
+        return (self::$brokenRFC2231 && !isset($output[$name]))
+            ? array_merge(array($name => self::encode($val, $charset)), $output)
+            : $output;
+    }
+
+    /**
+     * Decodes a MIME parameter string pursuant to RFC 2183 & 2231
+     * (Content-Type and Content-Disposition headers).
+     *
+     * @param string $string   The full header to decode (including the header
+     *                         name).
+     * @param string $charset  The charset the text should be decoded to.
+     *                         Defaults to system charset.
+     *
+     * @return array  An array with the following entries:
+     * <pre>
+     * 'params' - (array) The header's parameter values.
+     * 'val' - (string) The header's "base" value.
+     * </pre>
+     */
+    static public function decodeParam($string, $charset = null)
+    {
+        $convert = array();
+        $ret = array('params' => array(), 'val' => '');
+
+        /* Give $string a bogus body part or else decode() will complain. */
+        require_once 'Mail/mimeDecode.php';
+        $mime_decode = new Mail_mimeDecode($string . "\n\nA");
+        $res = $mime_decode->decode();
+
+        /* Are we dealing with content-type or content-disposition? */
+        if (isset($res->disposition)) {
+            $ret['val'] = $res->disposition;
+            $params = isset($res->d_parameters) ? $res->d_parameters : array();
+        } elseif (isset($res->ctype_primary)) {
+            $ret['val'] = $res->ctype_primary . '/' . $res->ctype_secondary;
+            $params = isset($res->ctype_parameters) ? $res->ctype_parameters : array();
+        } else {
+            return $ret;
+        }
+
+        /* Sort the params list. Prevents us from having to manually keep
+         * track of continuation values below. */
+        uksort($params, 'strnatcasecmp');
+
+        foreach ($params as $name => $val) {
+            /* Asterisk at end indicates encoded value. */
+            if (($encode = substr($name, -1)) == '*') {
+                $name = substr($name, 0, -1);
+            }
+
+            /* This asterisk indicates continuation parameter. */
+            if (($pos = strrpos($name, '*')) === false) {
+                $name = substr($name, 0, $pos);
+            }
+
+            if (!isset($ret['params'][$name])) {
+                $ret['params'][$name] = '';
+            }
+            $ret['params'][$name] .= $val;
+
+            if ($encode) {
+                $convert[$name] = true;
+            }
+        }
+
+        foreach (array_keys($convert) as $name) {
+            $val = $ret['params'][$name];
+            $quote = strpos($val, "'");
+            $orig_charset = substr($val, 0, $quote);
+            /* Ignore language. */
+            $quote = strpos($val, "'", $quote + 1);
+            substr($val, $quote + 1);
+            $ret['params'][$name] = String::convertCharset(urldecode(substr($val, $quote + 1)), $orig_charset, $charset);
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Generates a Message-ID string conforming to RFC 2822 [3.6.4] and the
+     * standards outlined in 'draft-ietf-usefor-message-id-01.txt'.
+     *
+     * @param string  A message ID string.
+     */
+    static public function generateMessageId()
+    {
+        return '<' . date('YmdHis') . '.' . self::generateRandomId() . '@' . $_SERVER['SERVER_NAME'] . '>';
+    }
+
+    /**
+     * Generates a Random-ID string suitable for use with MIME features that
+     * require a random string.
+     *
+     * @return string  A random string.
+     */
+    static public function generateRandomId()
+    {
+        return base_convert(dechex(strtr(microtime(), array('0.' => '', ' ' => ''))) . uniqid(), 16, 36);
+    }
+
+    /**
+     * Performs MIME ID "arithmetic" on a given ID.
+     *
+     * @param string $id      The MIME ID string.
+     * @param string $action  One of the following:
+     * <pre>
+     * 'down' - ID of child. Note: down will first traverse to "$id.0" if
+     *          given an ID *NOT* of the form "$id.0". If given an ID of the
+     *          form "$id.0", down will traverse to "$id.1". This behavior
+     *          can be avoided if 'norfc822' option is set.
+     * 'next' - ID of next sibling.
+     * 'prev' - ID of previous sibling.
+     * 'up' - ID of parent. Note: up will first traverse to "$id.0" if
+     *        given an ID *NOT* of the form "$id.0". If given an ID of the
+     *        form "$id.0", down will traverse to "$id". This behavior can be
+     *        avoided if 'norfc822' option is set.
+     * </pre>
+     * @param array $options  Additional options:
+     * <pre>
+     * 'count' - (integer) How many levels to traverse.
+     *           DEFAULT: 1
+     * 'norfc822' - (boolean) Don't traverse rfc822 sub-levels
+     *              DEFAULT: false
+     * </pre>
+     *
+     * @return mixed  The resulting ID string, or null if that ID can not
+     *                exist.
+     */
+    static public function mimeIdArithmetic($id, $action, $options = array())
+    {
+        $pos = strrpos($id, '.');
+        $end = ($pos === false) ? $id : substr($id, $pos + 1);
+
+        switch ($action) {
+        case 'down':
+            if ($end == '0') {
+                $id = ($pos === false) ? 1 : substr_replace($id, '1', $pos + 1);
+            } else {
+                $id .= empty($options['norfc822']) ? '.0' : '.1';
+            }
+            break;
+
+        case 'next':
+            ++$end;
+            $id = ($pos === false) ? $end : substr_replace($id, $end, $pos + 1);
+            break;
+
+        case 'prev':
+            if (($end == '0') ||
+                (empty($options['norfc822']) && ($end == '1'))) {
+                $id = null;
+            } elseif ($pos === false) {
+                $id = --$end;
+            } else {
+                $id = substr_replace($id, --$end, $pos + 1);
+            }
+            break;
+
+        case 'up':
+            if ($pos === false) {
+                $id = ($end == '0') ? null : '0';
+            } elseif (!empty($options['norfc822']) || ($end == '0')) {
+                $id = substr($id, 0, $pos);
+            } else {
+                $id = substr_replace($id, '0', $pos + 1);
+            }
+            break;
+        }
+
+        return (!is_null($id) && !empty($options['count']) && --$options['count'])
+            ? self::mimeIdArithmetic($id, $action, $options)
+            : $id;
+    }
+
+}
diff --git a/framework/Mime/lib/Horde/Mime/Address.php b/framework/Mime/lib/Horde/Mime/Address.php
new file mode 100644 (file)
index 0000000..d524155
--- /dev/null
@@ -0,0 +1,402 @@
+<?php
+/**
+ * The Horde_Mime_Address:: class provides methods for dealing with email
+ * address standards (RFC 822/2822/5322).
+ *
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime
+ */
+class Horde_Mime_Address
+{
+    /**
+     * Builds an RFC compliant email address.
+     *
+     * @param string $mailbox   Mailbox name.
+     * @param string $host      Domain name of mailbox's host.
+     * @param string $personal  Personal name phrase.
+     *
+     * @return string  The correctly escaped and quoted
+     *                 "$personal <$mailbox@$host>" string.
+     */
+    static public function writeAddress($mailbox, $host, $personal = '')
+    {
+        $address = '';
+
+        if (strlen($personal)) {
+            $address .= self::encode($personal, 'personal') . ' <';
+        }
+
+        $address .= self::encode($mailbox, 'address') . '@' . ltrim($host, '@');
+
+        if (strlen($personal)) {
+            $address .= '>';
+        }
+
+        return $address;
+    }
+
+    /**
+     * Write an RFC compliant group address, given the group name and a list
+     * of email addresses.
+     *
+     * @param string $groupname  The name of the group.
+     * @param array $addresses   The component email addresses. These e-mail
+     *                           addresses must be in RFC format.
+     *
+     * @return string  The correctly quoted group string.
+     */
+    static public function writeGroupAddress($groupname, $addresses = array())
+    {
+        return self::encode($groupname, 'address') . ':' . (empty($addresses) ? '' : implode(', ', $addresses)) . ';';
+    }
+
+    /**
+     * If an email address has no personal information, get rid of any angle
+     * brackets (<>) around it.
+     *
+     * @param string $address  The address to trim.
+     *
+     * @return string  The trimmed address.
+     */
+    static public function trimAddress($address)
+    {
+        $address = trim($address);
+
+        if (($address[0] == '<') && (substr($address, -1) == '>')) {
+            $address = substr($address, 1, -1);
+        }
+
+        return $address;
+    }
+
+    /**
+     * Explodes an RFC string, ignoring a delimiter if preceded by a "\"
+     * character, or if the delimiter is inside single or double quotes.
+     *
+     * @param string $string      The RFC compliant string.
+     * @param string $delimiters  A string containing valid delimiters.
+     *                            Defaults to ','.
+     *
+     * @return array  The exploded string in an array.
+     */
+    static public function explode($string, $delimiters = ',')
+    {
+        if (!strlen($string)) {
+            return array($string);
+        }
+
+        $emails = array();
+        $pos = 0;
+        $in_group = $in_quote = false;
+
+        for ($i = 0, $iMax = strlen($string); $i < $iMax; ++$i) {
+            $char = $string[$i];
+            if ($char == '"') {
+                if (!$i || ($prev !== '\\')) {
+                    $in_quote = !$in_quote;
+                }
+            } elseif ($in_group) {
+                if ($char == ';') {
+                    $emails[] = substr($string, $pos, $i - $pos + 1);
+                    $pos = $i + 1;
+                    $in_group = false;
+                }
+            } elseif (!$in_quote) {
+                if ($char == ':') {
+                    $in_group = true;
+                } elseif ((strpos($delimiters, $char) !== false) &&
+                          (!$i || ($prev !== '\\'))) {
+                    $emails[] = $i ? substr($string, $pos, $i - $pos) : '';
+                    $pos = $i + 1;
+                }
+            }
+            $prev = $char;
+        }
+
+        if ($pos != $i) {
+            /* The string ended without a delimiter. */
+            $emails[] = substr($string, $pos, $i - $pos);
+        }
+
+        return $emails;
+    }
+
+    /**
+     * Takes an address object array and formats it as a string.
+     *
+     * Object array format for the address "John Doe <john_doe@example.com>"
+     * is:
+     * <pre>
+     * 'personal' = Personal name ("John Doe")
+     * 'mailbox' = The user's mailbox ("john_doe")
+     * 'host' = The host the mailbox is on ("example.com")
+     * </pre>
+     *
+     * @param array $ob      The address object to be turned into a string.
+     * @param mixed $filter  A user@example.com style bare address to ignore.
+     *                       Either single string or an array of strings.  If
+     *                       the address matches $filter, an empty string will
+     *                       be returned.
+     *
+     * @return string  The formatted address.
+     */
+    static public function addrObject2String($ob, $filter = '')
+    {
+        /* If the personal name is set, decode it. */
+        $ob['personal'] = isset($ob['personal'])
+            ? Horde_Mime::decode($ob['personal'])
+            : '';
+
+        /* If both the mailbox and the host are empty, return an empty string.
+         * If we just let this case fall through, the call to writeAddress()
+         * will end up return just a '@', which is undesirable. */
+        if (empty($ob['mailbox']) && empty($ob['host'])) {
+            return '';
+        }
+
+        /* Make sure these two variables have some sort of value. */
+        if (!isset($ob['mailbox'])) {
+            $ob['mailbox'] = '';
+        } elseif ($ob['mailbox'] == 'undisclosed-recipients') {
+            return '';
+        }
+        if (!isset($ob['host'])) {
+            $ob['host'] = '';
+        }
+
+        /* Filter out unwanted addresses based on the $filter string. */
+        if ($filter) {
+            if (!is_array($filter)) {
+                $filter = array($filter);
+            }
+            foreach ($filter as $f) {
+                if (strcasecmp($f, $ob['mailbox'] . '@' . $ob['host']) == 0) {
+                    return '';
+                }
+            }
+        }
+
+        /* Return the formatted email address. */
+        return self::writeAddress($ob['mailbox'], $ob['host'], $ob['personal']);
+    }
+
+    /**
+     * Takes an array of address object arrays and passes each of them through
+     * addrObject2String().
+     *
+     * @param array $addresses  The array of address objects.
+     * @param mixed $filter     A user@example.com style bare address to
+     *                          ignore.  If any address matches $filter, it
+     *                          will not be included in the final string.
+     *
+     * @return string  All of the addresses in a comma-delimited string.
+     *                 Returns the empty string on error/no addresses found.
+     */
+    static public function addrArray2String($addresses, $filter = '')
+    {
+        if (!is_array($addresses)) {
+            return '';
+        }
+
+        $addrList = array();
+
+        foreach ($addresses as $addr) {
+            $val = self::addrObject2String($addr, $filter);
+            if (!empty($val)) {
+                $addrList[String::lower(self::bareAddress($val))] = $val;
+            }
+        }
+
+        return implode(', ', $addrList);
+    }
+
+    /**
+     * Return the list of addresses for a header object.
+     *
+     * @param array $obs  An array of header objects.
+     *
+     * @return array  An array of address information. Array elements:
+     * <pre>
+     * 'address' - (string) Full address
+     * 'display' - (string) A displayable version of the address
+     * 'groupname' - (string) The group name.
+     * 'host' - (string) Hostname
+     * 'inner' - (string) Trimmed, bare address
+     * 'personal' - (string) Personal string
+     * </pre>
+     */
+    static public function getAddressesFromObject($obs)
+    {
+        $ret = array();
+
+        if (!is_array($obs) || empty($obs)) {
+            return $ret;
+        }
+
+        foreach ($obs as $ob) {
+            if (isset($ob['groupname'])) {
+                $ret[] = array(
+                    'addresses' => self::getAddressesFromObject($ob['addresses']),
+                    'groupname' => $ob['groupname']
+                );
+                continue;
+            }
+
+            $ob = array_merge(array(
+                'host' => '',
+                'mailbox' => '',
+                'personal' => ''
+            ), $ob);
+
+            /* Ensure we're working with initialized values. */
+            if (!empty($ob['personal'])) {
+                $ob['personal'] = stripslashes(trim(Horde_Mime::decode($ob['personal']), '"'));
+            }
+
+            $inner = self::writeAddress($ob['mailbox'], $ob['host']);
+
+            /* Generate the new object. */
+            $ret[] = array(
+                'address' => self::addrObject2String($ob),
+                'display' => (empty($ob['personal']) ? '' : $ob['personal'] . ' <') . $inner . (empty($ob['personal']) ? '' : '>'),
+                'host' => $ob['host'],
+                'inner' => $inner,
+                'personal' => $ob['personal']
+            );
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Returns the bare address.
+     *
+     * @param string $address    The address string.
+     * @param string $defserver  The default domain to append to mailboxes.
+     * @param boolean $multiple  Should we return multiple results?
+     *
+     * @return mixed  If $multiple is false, returns the mailbox@host e-mail
+     *                address.  If $multiple is true, returns an array of
+     *                these addresses.
+     */
+    static public function bareAddress($address, $defserver = null,
+                                       $multiple = false)
+    {
+        $addressList = array();
+
+        $from = self::parseAddressList($address, array('defserver' => $defserver));
+        if (is_a($from, 'PEAR_Error')) {
+            return $multiple ? array() : '';
+        }
+
+        foreach ($from as $entry) {
+            if (!empty($entry['mailbox'])) {
+                $addressList[] = $entry['mailbox'] . (isset($entry['host']) ? '@' . $entry['host'] : '');
+            }
+        }
+
+        return $multiple ? $addressList : array_pop($addressList);
+    }
+
+    /**
+     * Parses a list of email addresses into its parts. Handles distribution
+     * lists.
+     *
+     * @param string $address  The address string.
+     * @param array $options   Additional options:
+     * <pre>
+     * 'defserver' - (string) The default domain to append to mailboxes.
+     *               DEFAULT: No domain appended.
+     * 'nestgroups' - (boolean) Nest the groups? (Will appear under the
+     *                'groupname' key)
+     *                DEFAULT: No.
+     * 'reterror' - (boolean) Return a PEAR_Error object on error?
+     *              DEFAULT: Returns an empty array on error.
+     * 'validate' - (boolean) Validate the address(es)?
+     *              DEFAULT: No.
+     * </pre>
+     *
+     * @return mixed  If 'reterror' is true, returns a PEAR_Error object on
+     *                error.  Otherwise, a list of arrays with the possible
+     *                keys: 'mailbox', 'host', 'personal', 'adl', 'groupname',
+     *                and 'comment'.
+     */
+    static public function parseAddressList($address, $options = array())
+    {
+        if (preg_match('/undisclosed-recipients:\s*;/i', trim($address))) {
+            return array();
+        }
+
+        $options = array_merge(array(
+            'defserver' => null,
+            'nestgroups' => false,
+            'reterror' => false,
+            'validate' => false
+        ), $options);
+
+        static $parser;
+        if (!isset($parser)) {
+            require_once 'Mail/RFC822.php';
+            $parser = new Mail_RFC822();
+        }
+
+        $ret = $parser->parseAddressList($address, $options['defserver'], $options['nestgroups'], $options['validate']);
+        if (is_a($ret, 'PEAR_Error')) {
+            return empty($options['reterror']) ? array() : $ret;
+        }
+
+        /* Convert objects to arrays. */
+        foreach (array_keys($ret) as $key) {
+            $ret[$key] = (array) $ret[$key];
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Quotes and escapes the given string if necessary using rules contained
+     * in RFC 2822 [3.2.5].
+     *
+     * @param string $str   The string to be quoted and escaped.
+     * @param string $type  Either 'address', or 'personal';
+     *
+     * @return string  The correctly quoted and escaped string.
+     */
+    static public function encode($str, $type = 'address')
+    {
+        // Excluded (in ASCII): 0-8, 10-31, 34, 40-41, 44, 58-60, 62, 64,
+        // 91-93, 127
+        $filter = "\0\1\2\3\4\5\6\7\10\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32\33\34\35\36\37\"(),:;<>@[\\]\177";
+
+        switch ($type) {
+        case 'personal':
+            // RFC 2822 [3.4]: Period not allowed in display name
+            $filter .= '.';
+            break;
+
+        case 'address':
+        default:
+            // RFC 2822 [3.4.1]: (HTAB, SPACE) not allowed in address
+            $filter .= "\11\40";
+            break;
+        }
+
+        // Strip double quotes if they are around the string already.
+        // If quoted, we know that the contents are already escaped, so
+        // unescape now.
+        $str = trim($str);
+        if ($str && ($str[0] == '"') && (substr($str, -1) == '"')) {
+            $str = stripslashes(substr($str, 1, -1));
+        }
+
+        return (strcspn($str, $filter) != strlen($str))
+            ? '"' . addcslashes($str, '\\"') . '"'
+            : $str;
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Headers.php b/framework/Mime/lib/Horde/Mime/Headers.php
new file mode 100644 (file)
index 0000000..2622fa1
--- /dev/null
@@ -0,0 +1,551 @@
+<?php
+/**
+ * The Horde_Mime_Headers:: class contains generic functions related to
+ * handling the headers of mail messages.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime
+ */
+class Horde_Mime_Headers
+{
+    /**
+     * The internal headers array.
+     *
+     * @var array
+     */
+    protected $_headers = array();
+
+    /**
+     * The sequence to use as EOL for the headers.
+     * The default is currently to output the EOL sequence internally as
+     * just "\n" instead of the canonical "\r\n" required in RFC 822 & 2045.
+     * To be RFC complaint, the full <CR><LF> EOL combination should be used
+     * when sending a message.
+     *
+     * @var string
+     */
+    protected $_eol = "\n";
+
+    /**
+     * The User-Agent string to use.
+     *
+     * @var string
+     */
+    protected $_agent = null;
+
+    /**
+     * Returns the internal header array in array format.
+     *
+     * @param array $options  Optional parameters:
+     * <pre>
+     * 'charset' => (string) Encodes the headers using this charset.
+     *              DEFAULT: No encoding.
+     * 'defserver' => (string) The default domain to append to mailboxes.
+     *              DEFAULT: No default name.
+     * 'nowrap' => (integer) Don't wrap the headers.
+     *             DEFAULT: Headers are wrapped.
+     * </pre>
+     *
+     * @return array  The headers in array format.
+     */
+    public function toArray($options = array())
+    {
+        $charset = empty($options['charset']) ? null : $options['charset'];
+        $address_keys = $charset ? array() : $this->addressFields();
+        $mime = $this->mimeParamFields();
+        $ret = array();
+
+        foreach ($this->_headers as $header => $ob) {
+            $val = is_array($ob['value']) ? $ob['value'] : array($ob['value']);
+
+            foreach (array_keys($val) as $key) {
+                if (in_array($header, $address_keys) ) {
+                    /* Address encoded headers. */
+                    $text = Horde_Mime::encodeAddress($val[$key], $charset, empty($options['defserver']) ? null : $options['defserver']);
+                    if (is_a($text, 'PEAR_Error')) {
+                        $text = $val[$key];
+                    }
+                } elseif (in_array($header, $mime) && !empty($ob['params'])) {
+                    /* MIME encoded headers (RFC 2231). */
+                    $text = $val[$key];
+                    foreach ($ob['params'] as $name => $param) {
+                        foreach (Horde_Mime::encodeParam($name, $param, $charset) as $name2 => $param2) {
+                            /* Escape certain characters in params (See RFC
+                             * 2045 [Appendix A]. */
+                            if (strcspn($param2, "\11\40\"(),/:;<=>?@[\\]") != strlen($param2)) {
+                                $param2 = '"' . addcslashes($param2, '\\"') . '"';
+                            }
+                            $text .= '; ' . $name2 . '=' . $param2;
+                        }
+                    }
+                } else {
+                    $text = $charset
+                        ? Horde_Mime::encode($val[$key], $charset)
+                        : $val[$key];
+                }
+
+                if (empty($options['nowrap'])) {
+                    /* Remove any existing linebreaks and wrap the line. */
+                    $header_text = $ob['header'] . ': ';
+                    $text = substr(wordwrap($header_text . strtr(trim($text), array("\r" => '', "\n" => '')), 76, $this->_eol . ' '), strlen($header_text));
+                }
+
+                $val[$key] = $text;
+            }
+
+            $ret[$ob['header']] = (count($val) == 1) ? reset($val) : $val;
+        }
+
+        return $ret;
+    }
+
+    /**
+     * Returns the internal header array in string format.
+     *
+     * @param array $options  Optional parameters:
+     * <pre>
+     * 'charset' => (string) Encodes the headers using this charset.
+     *              DEFAULT: No encoding.
+     * 'defserver' => (string) The default domain to append to mailboxes.
+     *              DEFAULT: No default name.
+     * 'nowrap' => (integer) Don't wrap the headers.
+     *             DEFAULT: Headers are wrapped.
+     * </pre>
+     *
+     * @return string  The headers in string format.
+     */
+    public function toString($options = array())
+    {
+        $text = '';
+
+        foreach ($this->toArray($options) as $key => $val) {
+            if (!is_array($val)) {
+                $val = array($val);
+            }
+            foreach ($val as $entry) {
+                $text .= $key . ': ' . $entry . $this->_eol;
+            }
+        }
+
+        return $text . $this->_eol;
+    }
+
+    /**
+     * Generate the 'Received' header for the Web browser->Horde hop
+     * (attempts to conform to guidelines in RFC 5321 [4.4]).
+     */
+    public function addReceivedHeader()
+    {
+        $old_error = error_reporting(0);
+        if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
+            /* This indicates the user is connecting through a proxy. */
+            $remote_path = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
+            $remote_addr = $remote_path[0];
+            $remote = gethostbyaddr($remote_addr);
+        } else {
+            $remote_addr = $_SERVER['REMOTE_ADDR'];
+            $remote = empty($_SERVER['REMOTE_HOST'])
+                ? gethostbyaddr($remote_addr)
+                : $_SERVER['REMOTE_HOST'];
+        }
+        error_reporting($old_error);
+
+        if (!empty($_SERVER['REMOTE_IDENT'])) {
+            $remote_ident = $_SERVER['REMOTE_IDENT'] . '@' . $remote . ' ';
+        } elseif ($remote != $_SERVER['REMOTE_ADDR']) {
+            $remote_ident = $remote . ' ';
+        } else {
+            $remote_ident = '';
+        }
+
+        if (!empty($GLOBALS['conf']['server']['name'])) {
+            $server_name = $GLOBALS['conf']['server']['name'];
+        } elseif (!empty($_SERVER['SERVER_NAME'])) {
+            $server_name = $_SERVER['SERVER_NAME'];
+        } elseif (!empty($_SERVER['HTTP_HOST'])) {
+            $server_name = $_SERVER['HTTP_HOST'];
+        } else {
+            $server_name = 'unknown';
+        }
+
+        $received = 'from ' . $remote . ' (' . $remote_ident .
+            '[' . $remote_addr . ']) ' .
+            'by ' . $server_name . ' (Horde Framework) with HTTP; ' .
+            date('r');
+
+        $this->addHeader('Received', $received);
+    }
+
+    /**
+     * Generate the 'Message-ID' header.
+     */
+    public function addMessageIdHeader()
+    {
+        $this->addHeader('Message-ID', Horde_Mime::generateMessageId());
+    }
+
+    /**
+     * Generate the 'Resent' headers (conforms to guidelines in
+     * RFC 2822 [3.6.6]).
+     *
+     * @param string $from  The address to use for 'Resent-From'.
+     * @param string $to    The address to use for 'Resent-To'.
+     */
+    public function addResentHeaders($from, $to)
+    {
+        /* We don't set Resent-Sender, Resent-Cc, or Resent-Bcc. */
+        $this->addHeader('Resent-Date', date('r'));
+        $this->addHeader('Resent-From', $from);
+        $this->addHeader('Resent-To', $to);
+        $this->addHeader('Resent-Message-ID', Horde_Mime::generateMessageId());
+    }
+
+    /**
+     * Generate the user agent description header.
+     */
+    public function addUserAgentHeader()
+    {
+        $this->addHeader('User-Agent', $this->getUserAgent());
+    }
+
+    /**
+     * Returns the user agent description header.
+     *
+     * @return string  The user agent header.
+     */
+    public function getUserAgent()
+    {
+        if (is_null($this->_agent)) {
+            $this->_agent = 'Horde Application Framework 4.0';
+        }
+        return $this->_agent;
+    }
+
+    /**
+     * Explicitly sets the User-Agent string.
+     *
+     * @param string $agent  The User-Agent string to use.
+     */
+    public function setUserAgent($agent)
+    {
+        $this->_agent = $agent;
+    }
+
+    /**
+     * Add a header to the header array.
+     *
+     * @param string $header  The header name.
+     * @param string $value   The header value.
+     * @param array $options  Additional options:
+     * <pre>
+     * 'decode' - (boolean) MIME decode the value?
+     * 'params' - (array) MIME parameters for Content-Type or
+     *            Content-Disposition
+     * </pre>
+     */
+    public function addHeader($header, $value, $options = array())
+    {
+        require_once 'Horde/String.php';
+
+        $header = trim($header);
+        $lcHeader = String::lower($header);
+
+        if (!isset($this->_headers[$lcHeader])) {
+            $this->_headers[$lcHeader] = array();
+            $this->_headers[$lcHeader]['header'] = $header;
+        }
+        $ptr = &$this->_headers[$lcHeader];
+
+        if (!empty($options['decode'])) {
+            // Fields defined in RFC 2822 that contain address information
+            if (in_array($lcHeader, $this->addressFields())) {
+                $value = Horde_Mime::decodeAddrString($value);
+            } else {
+                $value = Horde_Mime::decode($value);
+            }
+        }
+
+        if (isset($ptr['value'])) {
+            if (!is_array($ptr['value'])) {
+                $ptr['value'] = array($ptr['value']);
+            }
+            $ptr['value'][] = $value;
+        } else {
+            $ptr['value'] = $value;
+        }
+
+        if (!empty($options['params'])) {
+            $ptr['params'] = $options['params'];
+        }
+    }
+
+    /**
+     * Remove a header from the header array.
+     *
+     * @param string $header  The header name.
+     */
+    public function removeHeader($header)
+    {
+        require_once 'Horde/String.php';
+        unset($this->_headers[String::lower(trim($header))]);
+    }
+
+    /**
+     * Replace a value of a header.
+     *
+     * @param string $header  The header name.
+     * @param string $value   The header value.
+     * @param array $options  Additional options:
+     * <pre>
+     * 'decode' - (boolean) MIME decode the value?
+     * 'params' - (array) MIME parameters for Content-Type or
+     *            Content-Disposition
+     * </pre>
+     */
+    public function replaceHeader($header, $value, $options = array())
+    {
+        $this->removeHeader($header);
+        $this->addHeader($header, $value, $options);
+    }
+
+    /**
+     * Set a value for a particular header ONLY if that header is set.
+     *
+     * @param string $header  The header name.
+     * @param string $value   The header value.
+     * @param array $options  Additional options:
+     * <pre>
+     * 'decode' - (boolean) MIME decode the value?
+     * 'params' - (array) MIME parameters for Content-Type or
+     *            Content-Disposition
+     * </pre>
+     *
+     * @return boolean  True if value was set.
+     */
+    public function setValue($header, $value, $options = array())
+    {
+        require_once 'Horde/String.php';
+
+        if (isset($this->_headers[String::lower($header)])) {
+            $this->addHeader($header, $value, $decode);
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Attempts to return the header in the correct case.
+     *
+     * @param string $header  The header to search for.
+     *
+     * @return string  The value for the given header.
+     *                 If the header is not found, returns null.
+     */
+    public function getString($header)
+    {
+        require_once 'Horde/String.php';
+
+        $lcHeader = String::lower($header);
+        return (isset($this->_headers[$lcHeader]))
+            ? $this->_headers[$lcHeader]['header']
+            : null;
+    }
+
+    /**
+     * Attempt to return the value for a given header.
+     * The following header fields can only have 1 entry, so if duplicate
+     * entries exist, the first value will be used:
+     *   * To, From, Cc, Bcc, Date, Sender, Reply-to, Message-ID, In-Reply-To,
+     *     References, Subject (RFC 2822 [3.6])
+     *   * All List Headers (RFC 2369 [3])
+     * The values are not MIME encoded.
+     *
+     * @param string $header  The header to search for.
+     *
+     * @return mixed  The value for the given header.
+     *                If the header is not found, returns null.
+     */
+    public function getValue($header)
+    {
+        require_once 'Horde/String.php';
+
+        $entry = null;
+        $header = String::lower($header);
+
+        if (isset($this->_headers[$header])) {
+            $ptr = &$this->_headers[$header];
+            $entry = (is_array($ptr['value']) && in_array($header, $this->singleFields(true)))
+                ? $ptr['value'][0]
+                : $ptr['value'];
+            if (isset($ptr['params'])) {
+                foreach ($ptr['params'] as $key => $val) {
+                    $entry .= '; ' . $key . '=' . $val;
+                }
+            }
+        }
+
+        return $entry;
+    }
+
+    /**
+     * Returns the list of RFC defined header fields that contain address
+     * info.
+     *
+     * @return array  The list of headers, in lowercase.
+     */
+    public function addressFields()
+    {
+        return array(
+            'from', 'to', 'cc', 'bcc', 'reply-to', 'resent-to', 'resent-cc',
+            'resent-bcc', 'resent-from', 'sender'
+        );
+    }
+
+    /**
+     * Returns the list of RFC defined header fields that can only contain
+     * a single value.
+     *
+     * @param boolean $list  Return list-related headers also?
+     *
+     * @return array  The list of headers, in lowercase.
+     */
+    public function singleFields($list = true)
+    {
+        $single = array(
+            'to', 'from', 'cc', 'bcc', 'date', 'sender', 'reply-to',
+            'message-id', 'in-reply-to', 'references', 'subject', 'x-priority'
+        );
+
+        if ($list) {
+            $single = array_merge($single, array_keys($this->listHeaders()));
+        }
+
+        return $single;
+    }
+
+    /**
+     * Returns the list of RFC defined MIME header fields that may contain
+     * parameter info.
+     *
+     * @return array  The list of headers, in lowercase.
+     */
+    static public function mimeParamFields()
+    {
+        return array('content-type', 'content-disposition');
+    }
+
+    /**
+     * Returns the list of valid mailing list headers.
+     *
+     * @return array  The list of valid mailing list headers.
+     */
+    static public function listHeaders()
+    {
+        return array(
+            /* RFC 2369 */
+            'list-help'         =>  _("List-Help"),
+            'list-unsubscribe'  =>  _("List-Unsubscribe"),
+            'list-subscribe'    =>  _("List-Subscribe"),
+            'list-owner'        =>  _("List-Owner"),
+            'list-post'         =>  _("List-Post"),
+            'list-archive'      =>  _("List-Archive"),
+            /* RFC 2919 */
+            'list-id'           =>  _("List-Id")
+        );
+    }
+
+    /**
+     * Do any mailing list headers exist?
+     *
+     * @return boolean  True if any mailing list headers exist.
+     */
+    public function listHeadersExist()
+    {
+        return (bool) count(array_intersect(array_keys($this->listHeaders()), array_keys($this->_headers)));
+    }
+
+    /**
+     * Sets a new string to use for EOLs.
+     *
+     * @param string $eol  The string to use for EOLs.
+     */
+    public function setEOL($eol)
+    {
+        $this->_eol = $eol;
+    }
+
+    /**
+     * Get the string to use for EOLs.
+     *
+     * @return string  The string to use for EOLs.
+     */
+    public function getEOL()
+    {
+        return $this->_eol;
+    }
+
+    /**
+     * Returns a header from the header object.
+     *
+     * @param string $field  The header to return as an object.
+     *
+     * @return array  The object for the field requested.
+     */
+    public function getOb($field)
+    {
+        $val = $this->getValue($field);
+        return is_null($val)
+            ? array()
+            : Horde_Mime_Address::parseAddressList($val);
+    }
+
+    /**
+     * Builds a Horde_Mime_Headers object from header text.
+     * This function can be called statically:
+     *   $headers = Horde_Mime_Headers::parseHeaders().
+     *
+     * @param string $text  A text string containing the headers.
+     *
+     * @return Horde_Mime_Headers  A new Horde_Mime_Headers object.
+     */
+    static public function parseHeaders($text)
+    {
+        $headers = new Horde_Mime_Headers();
+        $currheader = $currtext = null;
+        $mime = self::mimeParamFields();
+
+        require_once 'Horde/String.php';
+
+        foreach (explode("\n", $text) as $val) {
+            $val = rtrim($val);
+            if (empty($val)) {
+                break;
+            }
+
+            if (($val[0] == ' ') || ($val[0] == "\t")) {
+                $currtext .= ' ' . ltrim($val);
+            } else {
+                if (!is_null($currheader)) {
+                    if (in_array(String::lower($currheader), $mime)) {
+                        $res = Horde_Mime::decodeParam($currheader . ': ' . $currtext);
+                        $headers->addHeader($currheader, $res['val'], array('decode' => true, 'params' => $res['params']));
+                    } else {
+                        $headers->addHeader($currheader, $currtext, array('decode' => true));
+                    }
+                }
+                $pos = strpos($val, ':');
+                $currheader = substr($val, 0, $pos);
+                $currtext = ltrim(substr($val, $pos + 1));
+            }
+        }
+        $headers->addHeader($currheader, $currtext, array('decode' => true));
+
+        return $headers;
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Magic.php b/framework/Mime/lib/Horde/Mime/Magic.php
new file mode 100644 (file)
index 0000000..0b6b633
--- /dev/null
@@ -0,0 +1,260 @@
+<?php
+
+require_once 'Horde/Util.php';
+
+/**
+ * The Horde_Mime_Magic:: class provides an interface to determine a MIME type
+ * for various content, if it provided with different levels of information.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime
+ */
+class Horde_Mime_Magic
+{
+    /**
+     * Returns a copy of the MIME extension map.
+     *
+     * @return array  The MIME extension map.
+     */
+    static protected function _getMimeExtensionMap()
+    {
+        static $mime_extension_map;
+
+        if (!isset($mime_extension_map)) {
+            require dirname(__FILE__) . '/mime.mapping.php';
+        }
+
+        return $mime_extension_map;
+    }
+
+    /**
+     * Returns a copy of the MIME magic file.
+     *
+     * @return array  The MIME magic file.
+     */
+    static protected function _getMimeMagicFile()
+    {
+        static $mime_magic;
+
+        if (!isset($mime_magic)) {
+            require dirname(__FILE__) . '/mime.magic.php';
+        }
+
+        return $mime_magic;
+    }
+
+    /**
+     * Attempt to convert a file extension to a MIME type, based
+     * on the global Horde and application specific config files.
+     *
+     * If we cannot map the file extension to a specific type, then
+     * we fall back to a custom MIME handler 'x-extension/$ext', which
+     * can be used as a normal MIME type internally throughout Horde.
+     *
+     * @param string $ext  The file extension to be mapped to a MIME type.
+     *
+     * @return string  The MIME type of the file extension.
+     */
+    static public function extToMime($ext)
+    {
+        if (empty($ext)) {
+           return 'application/octet-stream';
+        }
+
+        $ext = String::lower($ext);
+        $map = self::_getMimeExtensionMap();
+        $pos = 0;
+
+        while (!isset($map[$ext])) {
+            if (($pos = strpos($ext, '.')) === false) {
+                break;
+            }
+            $ext = substr($ext, $pos + 1);
+        }
+
+        return isset($map[$ext])
+            ? $map[$ext]
+            : 'x-extension/' . $ext;
+    }
+
+    /**
+     * Attempt to convert a filename to a MIME type, based on the global Horde
+     * and application specific config files.
+     *
+     * @param string $filename  The filename to be mapped to a MIME type.
+     * @param boolean $unknown  How should unknown extensions be handled? If
+     *                          true, will return 'x-extension/*' types.  If
+     *                          false, will return 'application/octet-stream'.
+     *
+     * @return string  The MIME type of the filename.
+     */
+    static public function filenameToMime($filename, $unknown = true)
+    {
+        $pos = strlen($filename) + 1;
+        $type = '';
+
+        $map = self::_getMimeExtensionMap();
+        for ($i = 0; $i <= $map['__MAXPERIOD__']; ++$i) {
+            $pos = strrpos(substr($filename, 0, $pos - 1), '.') + 1;
+            if ($pos === false) {
+                break;
+            }
+        }
+        $type = self::extToMime(substr($filename, $pos));
+
+        return (empty($type) || (!$unknown && (strpos($type, 'x-extension') !== false)))
+            ? 'application/octet-stream'
+            : $type;
+    }
+
+    /**
+     * Attempt to convert a MIME type to a file extension, based
+     * on the global Horde and application specific config files.
+     *
+     * If we cannot map the type to a file extension, we return false.
+     *
+     * @param string $type  The MIME type to be mapped to a file extension.
+     *
+     * @return string  The file extension of the MIME type.
+     */
+    static public function mimeToExt($type)
+    {
+        if (empty($type)) {
+            return false;
+        }
+
+        if (($key = array_search($type, self::_getMimeExtensionMap())) === false) {
+            list($major, $minor) = explode('/', $type);
+            if ($major == 'x-extension') {
+                return $minor;
+            }
+            if (strpos($minor, 'x-') === 0) {
+                return substr($minor, 2);
+            }
+            return false;
+        }
+
+        return $key;
+    }
+
+    /**
+     * Uses variants of the UNIX "file" command to attempt to determine the
+     * MIME type of an unknown file.
+     *
+     * @param string $path      The path to the file to analyze.
+     * @param string $magic_db  Path to the mime magic database.
+     *
+     * @return string  The MIME type of the file.  Returns false if the file
+     *                 type isn't recognized or an error happened.
+     */
+    static public function analyzeFile($path, $magic_db = null)
+    {
+        /* If the PHP Mimetype extension is available, use that. */
+        if (Util::extensionExists('fileinfo')) {
+            $res = empty($magic_db)
+                ? @finfo_open(FILEINFO_MIME)
+                : @finfo_open(FILEINFO_MIME, $magic_db);
+
+            if ($res) {
+                $type = finfo_file($res, $path);
+                finfo_close($res);
+
+                /* Remove any additional information. */
+                foreach (array(';', ',', '\\0') as $separator) {
+                    if (($pos = strpos($type, $separator)) !== false) {
+                        $type = rtrim(substr($type, 0, $pos));
+                    }
+                }
+
+                if (preg_match('|^[a-z0-9]+/[.-a-z0-9]+$|i', $type)) {
+                    return $type;
+                }
+            }
+        }
+
+        if (Util::extensionExists('mime_magic')) {
+            return trim(mime_content_type($path));
+        }
+
+        /* Use a built-in magic file. */
+        $mime_magic = self::_getMimeMagicFile();
+        if (!($fp = @fopen($path, 'rb'))) {
+            return false;
+        }
+
+        reset($mime_magic);
+        while (list($offset, $odata) = each($mime_magic)) {
+            reset($odata);
+            while (list($length, $ldata) = each($odata)) {
+                @fseek($fp, $offset, SEEK_SET);
+                $lookup = @fread($fp, $length);
+                if (!empty($ldata[$lookup])) {
+                    fclose($fp);
+                    return $ldata[$lookup];
+                }
+            }
+        }
+        fclose($fp);
+
+        return false;
+    }
+
+    /**
+     * Uses variants of the UNIX "file" command to attempt to determine the
+     * MIME type of an unknown byte stream.
+     *
+     * @param string $data      The file data to analyze.
+     * @param string $magic_db  Path to the mime magic database.
+     *
+     * @return string  The MIME type of the file.  Returns false if the file
+     *                 type isn't recognized or an error happened.
+     */
+    static public function analyzeData($data, $magic_db = null)
+    {
+        /* If the PHP Mimetype extension is available, use that. */
+        if (Util::extensionExists('fileinfo')) {
+            $res = empty($magic_db)
+                ? @finfo_open(FILEINFO_MIME)
+                : @finfo_open(FILEINFO_MIME, $magic_db);
+
+            if (!$res) {
+                return false;
+            }
+
+            $type = finfo_buffer($res, $data);
+            finfo_close($res);
+
+            /* Remove any additional information. */
+            if (($pos = strpos($type, ';')) !== false) {
+                $type = rtrim(substr($type, 0, $pos));
+            }
+
+            if (($pos = strpos($type, ',')) !== false) {
+                $type = rtrim(substr($type, 0, $pos));
+            }
+
+            return $type;
+        }
+
+        /* Use a built-in magic file. */
+        $mime_magic = self::_getMimeMagicFile();
+        reset($mime_magic);
+        while (list($offset, $odate) = each($mime_magic)) {
+            reset($odata);
+            while (list($length, $ldata) = each($odata)) {
+                $lookup = substr($data, $offset, $length);
+                if (!empty($ldata[$lookup])) {
+                    return $ldata[$lookup];
+                }
+            }
+        }
+
+        return false;
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Mail.php b/framework/Mime/lib/Horde/Mime/Mail.php
new file mode 100644 (file)
index 0000000..7eec596
--- /dev/null
@@ -0,0 +1,518 @@
+<?php
+
+require_once 'Horde/String.php';
+
+/**
+ * The Horde_Mime_Mail:: class wraps around the various MIME library classes
+ * to provide a simple interface for creating and sending MIME messages.
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Mime
+ */
+class Horde_Mime_Mail
+{
+    /**
+     * The message headers.
+     *
+     * @var Horde_Mime_Headers
+     */
+    protected $_headers;
+
+    /**
+     * The main body part.
+     *
+     * @var Horde_Mime_Part
+     */
+    protected $_body;
+
+    /**
+     * The main HTML body part.
+     *
+     * @var Horde_Mime_Part
+     */
+    protected $_htmlBody;
+
+    /**
+     * The message recipients.
+     *
+     * @var array
+     */
+    protected $_recipients = array();
+
+    /**
+     * All MIME parts except the main body part.
+     *
+     * @var array
+     */
+    protected $_parts = array();
+
+    /**
+     * The Mail driver name.
+     *
+     * @link http://pear.php.net/Mail
+     * @var string
+     */
+    protected $_mailer_driver = 'smtp';
+
+    /**
+     * The charset to use for the message.
+     *
+     * @var string
+     */
+    protected $_charset;
+
+    /**
+     * The Mail driver parameters.
+     *
+     * @link http://pear.php.net/Mail
+     * @var array
+     */
+    protected $_mailer_params = array();
+
+    /**
+     * Constructor.
+     *
+     * @param string $subject  The message subject.
+     * @param string $body     The message body.
+     * @param string $to       The message recipient(s).
+     * @param string $from     The message sender.
+     * @param string $charset  The character set of the message.
+     */
+    function __construct($subject = null, $body = null, $to = null,
+                         $from = null, $charset = 'iso-8859-1')
+    {
+        /* Set SERVER_NAME. */
+        if (!isset($_SERVER['SERVER_NAME'])) {
+            $_SERVER['SERVER_NAME'] = php_uname('n');
+        }
+
+        $this->_headers = new Horde_Mime_Headers();
+        $this->_charset = $charset;
+
+        if ($subject) {
+            $this->addHeader('Subject', $subject);
+        }
+        if ($to) {
+            $this->addHeader('To', $to);
+        }
+        if ($from) {
+            $this->addHeader('From', $from);
+        }
+        if ($body) {
+            $this->setBody($body, $charset);
+        }
+    }
+
+    /**
+     * Adds several message headers at once.
+     *
+     * @param array $header    Hash with header names as keys and header
+     *                         contents as values.
+     * @param string $charset  The header value's charset.
+     */
+    public function addHeaders($headers = array(), $charset = 'iso-8859-1')
+    {
+        foreach ($headers as $header => $value) {
+            if (is_a($added = $this->addHeader($header, $value, $charset), 'PEAR_Error')) {
+                return $added;
+            }
+        }
+    }
+
+    /**
+     * Adds a message header.
+     *
+     * @param string $header      The header name.
+     * @param string $value       The header value.
+     * @param string $charset     The header value's charset.
+     * @param boolean $overwrite  If true, an existing header of the same name
+     *                            is being overwritten; if false, multiple
+     *                            headers are added; if null, the correct
+     *                            behaviour is automatically chosen depending
+     *                            on the header name.
+     */
+    public function addHeader($header, $value, $charset = 'iso-8859-1',
+                              $overwrite = null)
+    {
+        $lc_header = String::lower($header);
+
+        /* Only encode value if charset is explicitly specified, otherwise
+         * the message's charset will be used when building the message. */
+        if (!empty($charset)) {
+            if (in_array($lc_header, $this->_headers->addressFields())) {
+                $value = Horde_Mime::encodeAddress($value, $charset);
+            } else {
+                $value = Horde_Mime::encode($value, $charset);
+            }
+        }
+
+        if (is_null($overwrite)) {
+            if (in_array($lc_header, $this->_headers->singleFields(true))) {
+                $overwrite = true;
+            }
+        }
+
+        if ($overwrite) {
+            $this->_headers->removeHeader($header);
+        }
+
+        if ($lc_header !== 'bcc') {
+            $this->_headers->addHeader($header, $value);
+        }
+
+        if (in_array($lc_header, array('to', 'cc', 'bcc'))) {
+            return $this->addRecipients($value);
+        }
+    }
+
+    /**
+     * Removes a message header.
+     *
+     * @param string $header  The header name.
+     */
+    public function removeHeader($header)
+    {
+        $value = $this->_headers->getValue($header);
+        $this->_headers->removeHeader($header);
+        if (in_array(String::lower($header), array('to', 'cc', 'bcc'))) {
+            $this->removeRecipients($value);
+        }
+    }
+
+    /**
+     * Sets the message body text.
+     *
+     * @param string $body             The message content.
+     * @param string $charset          The character set of the message.
+     * @param boolean|integer $wrap    If true, wrap the message at column 76;
+     *                                 If an integer wrap the message at that
+     *                                 column. Don't use wrapping if sending
+     *                                 flowed messages.
+     */
+    public function setBody($body, $charset = 'iso-8859-1', $wrap = false)
+    {
+        if ($wrap) {
+            $body = String::wrap($body, $wrap === true ? 76 : $wrap, "\n");
+        }
+        $this->_body = new Horde_Mime_Part();
+        $this->_body->setType('text/plain');
+        $this->_body->setCharset($charset);
+        $this->_body->setContents($body);
+    }
+
+    /**
+     * Sets the HTML message body text.
+     *
+     * @param string $body          The message content.
+     * @param string $charset       The character set of the message.
+     * @param boolean $alternative  If true, a multipart/alternative message is
+     *                              created and the text/plain part is
+     *                              generated automatically. If false, a
+     *                              text/html message is generated.
+     */
+    public function setHTMLBody($body, $charset = 'iso-8859-1',
+                                $alternative = true)
+    {
+        $this->_htmlBody = new Horde_Mime_Part();
+        $this->_htmlBody->setType('text/html');
+        $this->_htmlBody->setCharset($charset);
+        $this->_htmlBody->setContents($body);
+        if ($alternative) {
+            require_once 'Horde/Text/Filter.php';
+            $this->setBody(Text_Filter::filter($body, 'html2text', array('wrap' => false), $charset));
+        }
+    }
+
+    /**
+     * Adds a message part.
+     *
+     * @param string $mime_type    The content type of the part.
+     * @param string $content      The content of the part.
+     * @param string $charset      The character set of the part.
+     * @param string $disposition  The content disposition of the part.
+     *
+     * @return integer  The part number.
+     */
+    public function addPart($mime_type, $content, $charset = 'us-ascii',
+                            $disposition = null)
+    {
+        $part = new Horde_Mime_Part();
+        $part->setType($mime_type);
+        $part->setCharset($charset);
+        $part->setDisposition($disposition);
+        $part->setContents($content);
+        return $this->addMimePart($part);
+    }
+
+    /**
+     * Adds a MIME message part.
+     *
+     * @param Horde_Mime_Part $part  A Horde_Mime_Part object.
+     *
+     * @return integer  The part number.
+     */
+    public function addMimePart($part)
+    {
+        $part->transferEncodeContents();
+        $this->_parts[] = $part;
+        return count($this->_parts) - 1;
+    }
+
+    /**
+     * Adds an attachment.
+     *
+     * @param string $file     The path to the file.
+     * @param string $name     The file name to use for the attachment.
+     * @param string $type     The content type of the file.
+     * @param string $charset  The character set of the part (only relevant for
+     *                         text parts.
+     *
+     * @return integer  The part number.
+     */
+    public function addAttachment($file, $name = null, $type = null,
+                                  $charset = 'us-ascii')
+    {
+        if (empty($name)) {
+            $name = basename($file);
+        }
+
+        if (empty($type)) {
+            require_once dirname(__FILE__) . '/Magic.php';
+            $type = Horde_Mime_Magic::filenameToMime($file, false);
+        }
+
+        $num = $this->addPart($type, file_get_contents($file), $charset, 'attachment');
+        $this->_parts[$num]->setName($name);
+        return $num;
+    }
+
+    /**
+     * Removes a message part.
+     *
+     * @param integer $part  The part number.
+     */
+    public function removePart($part)
+    {
+        if (isset($this->_parts[$part])) {
+            unset($this->_parts[$part]);
+        }
+    }
+
+    /**
+     * Adds message recipients.
+     *
+     * Recipients specified by To:, Cc:, or Bcc: headers are added
+     * automatically.
+     *
+     * @param string|array  List of recipients, either as a comma separated
+     *                      list or as an array of email addresses.
+     */
+    public function addRecipients($recipients)
+    {
+        $recipients = $this->_buildRecipients($recipients);
+        if (is_a($recipients, 'PEAR_Error')) {
+            return $recipients;
+        }
+        $this->_recipients = array_merge($this->_recipients, $recipients);
+    }
+
+    /**
+     * Removes message recipients.
+     *
+     * @param string|array  List of recipients, either as a comma separated
+     *                      list or as an array of email addresses.
+     */
+    public function removeRecipients($recipients)
+    {
+        $recipients = $this->_buildRecipients($recipients);
+        if (is_a($recipients, 'PEAR_Error')) {
+            return $recipients;
+        }
+        $this->_recipients = array_diff($this->_recipients, $recipients);
+    }
+
+    /**
+     * Removes all message recipients.
+     */
+    public function clearRecipients()
+    {
+        $this->_recipients = array();
+    }
+
+    /**
+     * Builds a recipients list.
+     *
+     * @param string|array  List of recipients, either as a comma separated
+     *                      list or as an array of email addresses.
+     *
+     * @return array  Normalized list of recipients or PEAR_Error on failure.
+     */
+    protected function _buildRecipients($recipients)
+    {
+        if (is_string($recipients)) {
+            $recipients = Horde_Mime::explode($recipients, ',');
+        }
+        $recipients = array_filter(array_map('trim', $recipients));
+
+        $addrlist = array();
+        foreach ($recipients as $email) {
+            if (!empty($email)) {
+                $unique = Horde_Mime::bareAddress($email);
+                if ($unique) {
+                    $addrlist[$unique] = $email;
+                } else {
+                    $addrlist[$email] = $email;
+                }
+            }
+        }
+
+        foreach (Horde_Mime::bareAddress(implode(', ', $addrlist), null, true) as $val) {
+            if (Horde_Mime::is8bit($val)) {
+                return PEAR::raiseError(sprintf(_("Invalid character in e-mail address: %s."), $val));
+            }
+        }
+
+        return $addrlist;
+    }
+
+    /**
+     * Sends this message.
+     *
+     * For the possible Mail drivers and parameters see the PEAR Mail
+     * documentation.
+     * @link http://pear.php.net/Mail
+     *
+     * @param string $driver   The Mail driver to use.
+     * @param array $params    Any parameters necessary for the Mail driver.
+     * @param boolean $resend  If true, the message id and date are re-used;
+     *                         If false, they will be updated.
+     * @param boolean $flowed  Send message in flowed text format. @since
+     *                         Horde 3.2.1
+     *
+     * @return mixed  True on success, PEAR_Error on error.
+     */
+    public function send($driver = null, $params = array(), $resend = false,
+                         $flowed = true)
+    {
+        /* Add mandatory headers if missing. */
+        if (!$resend || !$this->_headers->getValue('Message-ID')) {
+            $this->_headers->addMessageIdHeader();
+        }
+        if (!$this->_headers->getValue('User-Agent')) {
+            $this->_headers->addAgentHeader();
+        }
+        if (!$resend || !$this->_headers->getValue('Date')) {
+            $this->_headers->addHeader('Date', date('r'));
+        }
+
+        /* Send in flowed format. */
+        if ($flowed && !empty($this->_body)) {
+            require_once 'Text/Flowed.php';
+            $flowed = new Text_Flowed($this->_body->getContents(),
+                                      $this->_body->getCharset());
+            $flowed->setDelSp(true);
+            $this->_body->setContentTypeParameter('format', 'flowed');
+            $this->_body->setContentTypeParameter('DelSp', 'Yes');
+            $this->_body->setContents($flowed->toFlowed());
+        }
+
+        /* Build mime message. */
+        $mime = new Horde_Mime_Part();
+        if (!empty($this->_body) && !empty($this->_htmlBody)) {
+            $basepart = new Horde_Mime_Part();
+            $basepart->setType('multipart/alternative');
+            $this->_body->setDescription(_("Plaintext Version of Message"));
+            $basepart->addPart($this->_body);
+            $this->_htmlBody->setDescription(_("HTML Version of Message"));
+            $basepart->addPart($this->_htmlBody);
+            $mime->addPart($basepart);
+        } elseif (!empty($this->_htmlBody)) {
+            $mime->addPart($this->_htmlBody);
+        } elseif (!empty($this->_body)) {
+            $mime->addPart($this->_body);
+        }
+        foreach ($this->_parts as $mime_part) {
+            $mime->addPart($mime_part);
+        }
+
+        /* Check mailer configuration. */
+        if (!empty($driver)) {
+            $this->_mailer_driver = $driver;
+        }
+        if (!empty($params)) {
+            $this->_mailer_params = $params;
+        }
+
+        /* Send message. */
+        return $mime->send(implode(', ', $this->_recipients), $this->_headers,
+                           $this->_mailer_driver, $this->_mailer_params);
+    }
+
+    /**
+     * Return error string corresponding to a sendmail error code.
+     *
+     * @param integer $code  The error code.
+     *
+     * @return string  The error string, or null if the code is unknown.
+     */
+    static public function sendmailError($code)
+    {
+        switch ($code) {
+        case 64: // EX_USAGE
+            return 'sendmail: ' . _("command line usage error") . ' (64)';
+
+        case 65: // EX_DATAERR
+            return 'sendmail: ' . _("data format error") . ' (65)';
+
+        case 66: // EX_NOINPUT
+            return 'sendmail: ' . _("cannot open input") . ' (66)';
+
+        case 67: // EX_NOUSER
+            return 'sendmail: ' . _("addressee unknown") . ' (67)';
+
+        case 68: // EX_NOHOST
+            return 'sendmail: ' . _("host name unknown") . ' (68)';
+
+        case 69: // EX_UNAVAILABLE
+            return 'sendmail: ' . _("service unavailable") . ' (69)';
+
+        case 70: // EX_SOFTWARE
+            return 'sendmail: ' . _("internal software error") . ' (70)';
+
+        case 71: // EX_OSERR
+            return 'sendmail: ' . _("system error") . ' (71)';
+
+        case 72: // EX_OSFILE
+            return 'sendmail: ' . _("critical system file missing") . ' (72)';
+
+        case 73: // EX_CANTCREAT
+            return 'sendmail: ' . _("cannot create output file") . ' (73)';
+
+        case 74: // EX_IOERR
+            return 'sendmail: ' . _("input/output error") . ' (74)';
+
+        case 75: // EX_TEMPFAIL
+            return 'sendmail: ' . _("temporary failure") . ' (75)';
+
+        case 76: // EX_PROTOCOL
+            return 'sendmail: ' . _("remote error in protocol") . ' (76)';
+
+        case 77: // EX_NOPERM
+            return 'sendmail: ' . _("permission denied") . ' (77)';
+
+        case 78: // EX_CONFIG
+            return 'sendmail: ' . _("configuration error") . ' (78)';
+
+        case 79: // EX_NOTFOUND
+            return 'sendmail: ' . _("entry not found") . ' (79)';
+
+        default:
+            return null;
+        }
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Mdn.php b/framework/Mime/lib/Horde/Mime/Mdn.php
new file mode 100644 (file)
index 0000000..93a2e10
--- /dev/null
@@ -0,0 +1,239 @@
+<?php
+/**
+ * The Horde_Mime_Mdn:: class implements Message Disposition Notifications as
+ * described by RFC 3798.
+ *
+ * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime
+ */
+class Horde_Mime_Mdn
+{
+    /**
+     * The Horde_Mime_Headers object.
+     *
+     * @var Horde_Mime_Headers
+     */
+    protected $_headers;
+
+    /**
+     * The text of the original message.
+     *
+     * @var string
+     */
+    protected $_msgtext = false;
+
+    /**
+     * Constructor.
+     *
+     * @param Horde_Mime_Headers $mime_headers  A Horde_Mime_Headers object.
+     */
+    function __construct($headers = null)
+    {
+        $this->_headers = $headers;
+    }
+
+    /**
+     * Returns the address to return the MDN to.
+     *
+     * @return string  The address to send the MDN to. Returns null if no
+     *                 MDN is requested.
+     */
+    public function getMdnReturnAddr()
+    {
+        /* RFC 3798 [2.1] requires the Disposition-Notificaion-To header
+         * for an MDN to be created. */
+        return $this->_headers->getValue('Disposition-Notification-To');
+    }
+
+    /**
+     * Is user input required to send the MDN?
+     * Explicit confirmation is needed in some cases to prevent mail loops
+     * and the use of MDNs for mail bombing.
+     *
+     * @return boolean  Is explicit user input required to send the MDN?
+     */
+    public function userConfirmationNeeded()
+    {
+        $return_path = $this->_headers->getValue('Return-Path');
+
+        /* RFC 3798 [2.1]: Explicit confirmation is needed if there is no
+         * Return-Path in the header. Also, "if the message contains more
+         * than one Return-Path header, the implementation may [] treat the
+         * situation as a failure of the comparison." */
+        if (empty($return_path) || is_array($return_path)) {
+            return true;
+        }
+
+        require_once dirname(__FILE__) . '/Address.php';
+
+        /* RFC 3798 [2.1]: Explicit confirmation is needed if there is more
+         * than one distinct address in the Disposition-Notification-To
+         * header. */
+        $addr_arr = Horde_Mime_Address::parseAddressList($this->getMdnReturnAddr());
+        if (count($addr_arr) > 1) {
+            return true;
+        }
+
+        /* RFC 3798 [2.1] states that "MDNs SHOULD NOT be sent automatically
+         * if the address in the Disposition-Notification-To header differs
+         * from the address in the Return-Path header." This comparison is
+         * case-sensitive for the mailbox part and case-insensitive for the
+         * host part. */
+        $ret_arr = Horde_Mime_Address::parseAddressList($return_path);
+        return ($addr_arr[0]['mailbox'] == $ret_arr[0]['mailbox']) &&
+               (String::lower($addr_arr[0]['host']) == String::lower($ret_arr[0]['host']));
+    }
+
+    /**
+     * When generating the MDN, should we return the enitre text of the
+     * original message?  The default is no - we only return the headers of
+     * the original message. If the text is passed in via this method, we
+     * will return the entire message.
+     *
+     * @param string $text  The text of the original message.
+     */
+    public function originalMessageText($text)
+    {
+        $this->_msgtext = $text;
+    }
+
+    /**
+     * Generate the MDN according to the specifications listed in RFC
+     * 3798 [3].
+     *
+     * @param boolean $action   Was this MDN type a result of a manual action
+     *                          on part of the user?
+     * @param boolean $sending  Was this MDN sent as a result of a manual
+     *                          action on part of the user?
+     * @param string $type      The type of action performed by the user.
+     * <pre>
+     * Per RFC 3798 [3.2.6.2] the following types are valid:
+     * =====================================================
+     * 'displayed'
+     * 'deleted'
+     * </pre>
+     * @param array $mod        The list of modifications.
+     * <pre>
+     * Per RFC 3798 [3.2.6.3] the following modifications are valid:
+     * =============================================================
+     * 'error'
+     * </pre>
+     * @param array $err        If $mod is 'error', the additional information
+     *                          to provide.  Key is the type of modification,
+     *                          value is the text.
+     *
+     * @return mixed  True on success, PEAR_Error object on error.
+     */
+    public function generate($action, $sending, $type, $mod = array(),
+                             $err = array())
+    {
+        require_once dirname(__FILE__) . '/Headers.php';
+        require_once dirname(__FILE__) . '/Part.php';
+        require_once 'Horde/Identity.php';
+        require_once 'Horde/Text.php';
+
+        /* Set up some variables we use later. */
+        $identity = &Identity::singleton();
+        $from_addr = $identity->getDefaultFromAddress();
+
+        $to = $this->getMdnReturnAddr();
+        $ua = $this->_headers->getAgentHeader();
+
+        $orig_recip = $this->_headers->getValue('Original-Recipient');
+        if (!empty($orig_recip) && is_array($orig_recip)) {
+            $orig_recip = $orig_recip[0];
+        }
+
+        $msg_id = $this->_headers->getValue('Message-ID');
+
+        /* Create the Disposition field now (RFC 3798 [3.2.6]). */
+        $dispo = 'Disposition: ' .
+                 (($action) ? 'manual-action' : 'automatic-action') .
+                 '/' .
+                 (($sending) ? 'MDN-sent-manually' : 'MDN-sent-automatically') .
+                 '; ' .
+                 $type;
+        if (!empty($mod)) {
+            $dispo .= '/' . implode(', ', $mod);
+        }
+
+        /* Set up the mail headers. */
+        $msg_headers = new Horde_Mime_Headers();
+        $msg_headers->addMessageIdHeader();
+        $msg_headers->addAgentHeader($ua);
+        $msg_headers->addHeader('Date', date('r'));
+        $msg_headers->addHeader('From', $from_addr);
+        $msg_headers->addHeader('To', $this->getMdnReturnAddr());
+        $msg_headers->addHeader('Subject', _("Disposition Notification"));
+
+        /* MDNs are a subtype of 'multipart/report'. */
+        $msg = new Horde_Mime_Part();
+        $msg->setType('multipart/report');
+        $msg->setContentTypeParameter('report-type', 'disposition-notification');
+
+        $charset = NLS::getCharset();
+
+        /* The first part is a human readable message. */
+        $part_one = new Horde_Mime_Part('text/plain');
+        $part_one->setCharset($charset);
+        if ($type == 'displayed') {
+            $contents = sprintf(_("The message sent on %s to %s with subject \"%s\" has been displayed.\n\nThis is no guarantee that the message has been read or understood."), $this->_headers->getValue('Date'), $this->_headers->getValue('To'), $this->_headers->getValue('Subject'));
+            require_once 'Text/Flowed.php';
+            $flowed = new Text_Flowed($contents, $charset);
+            $flowed->setDelSp(true);
+            $part_one->setContentTypeParameter('format', 'flowed');
+            $part_one->setContentTypeParameter('DelSp', 'Yes');
+            $part_one->setContents($flowed->toFlowed());
+        }
+        // TODO: Messages for other notification types.
+        $msg->addPart($part_one);
+
+        /* The second part is a machine-parseable description. */
+        $part_two = new Horde_Mime_Part('message/disposition-notification');
+        $part_two->setContents('Reporting-UA: ' . $GLOBALS['conf']['server']['name'] . '; ' . $ua . "\n");
+        if (!empty($orig_recip)) {
+            $part_two->appendContents('Original-Recipient: rfc822;' . $orig_recip . "\n");
+        }
+        $part_two->appendContents('Final-Recipient: rfc822;' . $from_addr . "\n");
+        if (!empty($msg_id)) {
+            $part_two->appendContents('Original-Message-ID: rfc822;' . $msg_id . "\n");
+        }
+        $part_two->appendContents($dispo . "\n");
+        if (in_array('error', $mod) && isset($err['error'])) {
+            $part_two->appendContents('Error: ' . $err['error'] . "\n");
+        }
+        $msg->addPart($part_two);
+
+        /* The third part is the text of the original message.  RFC 3798 [3]
+         * allows us to return only a portion of the entire message - this
+         * is left up to the user. */
+        $part_three = new Horde_Mime_Part('message/rfc822');
+        $part_three->setContents($this->_headers->toString());
+        if (!empty($this->_msgtext)) {
+            $part_three->appendContents($part_three->getEOL() . $this->_msgtext);
+        }
+        $msg->addPart($part_three);
+
+        return $msg->send($to, $msg_headers);
+    }
+
+    /**
+     * Add a MDN (read receipt) request headers to the Horde_Mime_Headers::
+     * object.
+     *
+     * @param Horde_Mime_Headers $ob   The object to add the headers to.
+     * @param string $to               The address the receipt should be
+     *                                 mailed to.
+     */
+    public function addMdnRequestHeaders($ob, $to)
+    {
+        /* This is the RFC 3798 way of requesting a receipt. */
+        $ob->addHeader('Disposition-Notification-To', $to);
+    }
+
+}
diff --git a/framework/Mime/lib/Horde/Mime/Part.php b/framework/Mime/lib/Horde/Mime/Part.php
new file mode 100644 (file)
index 0000000..a9f5f99
--- /dev/null
@@ -0,0 +1,1646 @@
+<?php
+
+require_once 'Horde/String.php';
+require_once dirname(__FILE__) . '/../Mime.php';
+
+/**
+ * The Horde_Mime_Part:: class provides a wrapper around MIME parts and
+ * methods for dealing with them.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime
+ */
+class Horde_Mime_Part
+{
+    /* The character(s) used internally for EOLs. */
+    const EOL = "\n";
+
+    /* The character string designated by RFC 2045 to designate EOLs in MIME
+     * messages. */
+    const RFC_EOL = "\r\n";
+
+    /* The default MIME character set. */
+    const DEFAULT_CHARSET = 'us-ascii';
+
+    /* The default MIME disposition. */
+    const DEFAULT_DISPOSITION = 'inline';
+
+    /* The default MIME encoding. */
+    const DEFAULT_ENCODING = '7bit';
+
+    /**
+     * The type (ex.: text) of this part.
+     * Per RFC 2045, the default is 'application'.
+     *
+     * @var string
+     */
+    protected $_type = 'application';
+
+    /**
+     * The subtype (ex.: plain) of this part.
+     * Per RFC 2045, the default is 'octet-stream'.
+     *
+     * @var string
+     */
+    protected $_subtype = 'octet-stream';
+
+    /**
+     * The body of the part.
+     *
+     * @var string
+     */
+    protected $_contents = '';
+
+    /**
+     * The desired transfer encoding of this part.
+     *
+     * @var string
+     */
+    protected $_transferEncoding = self::DEFAULT_ENCODING;
+
+    /**
+     * Should the message be encoded via 7-bit?
+     *
+     * @var boolean
+     */
+    protected $_encode7bit = true;
+
+    /**
+     * The description of this part.
+     *
+     * @var string
+     */
+    protected $_description = '';
+
+    /**
+     * The disposition of this part (inline or attachment).
+     *
+     * @var string
+     */
+    protected $_disposition = self::DEFAULT_DISPOSITION;
+
+    /**
+     * The disposition parameters of this part.
+     *
+     * @var array
+     */
+    protected $_dispParams = array();
+
+    /**
+     * The content type parameters of this part.
+     *
+     * @var array
+     */
+    protected $_contentTypeParams = array();
+
+    /**
+     * The subparts of this part.
+     *
+     * @var array
+     */
+    protected $_parts = array();
+
+    /**
+     * Information/Statistics on the subpart.
+     *
+     * @var array
+     */
+    protected $_information = array();
+
+    /**
+     * The MIME ID of this part.
+     *
+     * @var string
+     */
+    protected $_mimeid = null;
+
+    /**
+     * The sequence to use as EOL for this part.
+     * The default is currently to output the EOL sequence internally as
+     * just "\n" instead of the canonical "\r\n" required in RFC 822 & 2045.
+     * To be RFC complaint, the full <CR><LF> EOL combination should be used
+     * when sending a message.
+     * It is not crucial here since the PHP/PEAR mailing functions will handle
+     * the EOL details.
+     *
+     * @var string
+     */
+    protected $_eol = self::EOL;
+
+    /**
+     * Internal class flags.
+     *
+     * @var array
+     */
+    protected $_flags = array();
+
+    /**
+     * Unique Horde_Mime_Part boundary string.
+     *
+     * @var string
+     */
+    protected $_boundary = null;
+
+    /**
+     * Default value for this Part's size.
+     *
+     * @var integer
+     */
+    protected $_bytes = 0;
+
+    /**
+     * The content-ID for this part.
+     *
+     * @var string
+     */
+    protected $_contentid = null;
+
+    /**
+     * Do we need to reindex the current part.
+     *
+     * @var boolean
+     */
+    protected $_reindex = false;
+
+    /**
+     * Is this the base MIME part?
+     *
+     * @var boolean
+     */
+    protected $_basepart = false;
+
+    /**
+     * Set the content-disposition of this part.
+     *
+     * @param string $disposition  The content-disposition to set (inline or
+     *                             attachment).
+     */
+    public function setDisposition($disposition)
+    {
+        $disposition = String::lower($disposition);
+
+        if (in_array($disposition, array('inline', 'attachment'))) {
+            $this->_disposition = $disposition;
+        }
+    }
+
+    /**
+     * Get the content-disposition of this part.
+     *
+     * @return string  The part's content-disposition.
+     */
+    public function getDisposition()
+    {
+        return $this->_disposition;
+    }
+
+    /**
+     * Add a disposition parameter to this part.
+     *
+     * @param string $label  The disposition parameter label.
+     * @param string $data   The disposition parameter data.
+     */
+    public function setDispositionParameter($label, $data)
+    {
+        $this->_dispParams[$label] = $data;
+    }
+
+    /**
+     * Get a disposition parameter from this part.
+     *
+     * @param string $label  The disposition parameter label.
+     *
+     * @return string  The data requested.
+     *                 Returns null if $label is not set.
+     */
+    public function getDispositionParameter($label)
+    {
+        return (isset($this->_dispParams[$label]))
+            ? $this->_dispParams[$label]
+            : null;
+    }
+
+    /**
+     * Get all parameters from the Content-Disposition header.
+     *
+     * @return array  An array of all the parameters
+     *                Returns the empty array if no parameters set.
+     */
+    public function getAllDispositionParameters()
+    {
+        return $this->_dispParams;
+    }
+
+    /**
+     * Set the name of this part.
+     *
+     * @param string $name  The name to set.
+     */
+    public function setName($name)
+    {
+        $this->setContentTypeParameter('name', $name);
+    }
+
+    /**
+     * Get the name of this part.
+     *
+     * @param boolean $default  If the name parameter doesn't exist, should we
+     *                          use the default name from the description
+     *                          parameter?
+     *
+     * @return string  The name of the part.
+     */
+    public function getName($default = false)
+    {
+        $name = $this->getContentTypeParameter('name');
+
+        if ($default && empty($name)) {
+            $name = preg_replace('|\W|', '_', $this->getDescription(false));
+        }
+
+        return $name;
+    }
+
+    /**
+     * Set the body contents of this part.
+     *
+     * @param string $contents  The part body.
+     * @param string $encoding  The current encoding of the contents.
+     */
+    public function setContents($contents, $encoding = null)
+    {
+        $this->_contents = $contents;
+        $this->_flags['contentsSet'] = true;
+        $this->_flags['currentEncoding'] = is_null($encoding) ? $this->getCurrentEncoding() : $encoding;
+    }
+
+    /**
+     * Add to the body contents of this part.
+     *
+     * @param string $contents  The contents to append to the current part
+     *                          body.
+     * @param string $encoding  The current encoding of the contents. If not
+     *                          specified, will try to auto determine the
+     *                          encoding.
+     */
+    public function appendContents($contents, $encoding = null)
+    {
+        if (empty($this->_flags['contentsSet'])) {
+            $this->setContents($contents, $encoding);
+        } else {
+            if (!is_null($encoding) &&
+                ($encoding != $this->getCurrentEncoding())) {
+                $this->setTransferEncoding($encoding);
+                $this->transferDecodeContents();
+            }
+            $this->setContents($this->_contents . $contents, $encoding);
+        }
+    }
+
+    /**
+     * Clears the body contents of this part.
+     */
+    public function clearContents()
+    {
+        $this->_contents = '';
+        unset($this->_flags['contentsSet'], $this->_flags['currentEncoding']);
+    }
+
+    /**
+     * Return the body of the part.
+     *
+     * @return string  The raw body of the part.
+     */
+    public function getContents()
+    {
+        return $this->_contents;
+    }
+
+    /**
+     * Returns the contents in strict RFC 822 & 2045 output - namely, all
+     * newlines end with the canonical <CR><LF> sequence.
+     *
+     * @return string  The raw body of the part, with <CR><LF> EOL..
+     */
+    public function getCanonicalContents()
+    {
+        return $this->replaceEOL($this->_contents, self::RFC_EOL);
+    }
+
+    /**
+     * Transfer encode the contents (to the transfer encoding identified via
+     * getTransferEncoding()) and set as the part's new contents.
+     */
+    public function transferEncodeContents()
+    {
+        $contents = $this->transferEncode();
+        $encode = $this->_flags['currentEncoding'] = $this->_flags['lastTransferEncode'];
+        $this->setContents($contents, $encode);
+        $this->setTransferEncoding($encode);
+    }
+
+    /**
+     * Transfer decode the contents and set them as the new contents.
+     */
+    public function transferDecodeContents()
+    {
+        $contents = $this->transferDecode();
+        $encode = $this->_flags['currentEncoding'] = $this->_flags['lastTransferDecode'];
+        $this->setTransferEncoding($encode);
+
+        /* Don't set contents if they are empty, because this will do stuff
+           like reset the internal bytes field, even though we shouldn't do
+           that (the user has their reasons to set the bytes field to a
+           non-zero value without putting the contents into this part). */
+        if (strlen($contents)) {
+            $this->setContents($contents, $encode);
+        }
+    }
+
+    /**
+     * Set the MIME type of this part.
+     *
+     * @param string $mimetype  The MIME type to set (ex.: text/plain).
+     */
+    public function setType($mimetype)
+    {
+        /* RFC 2045: Any entity with unrecognized encoding must be treated
+           as if it has a Content-Type of "application/octet-stream"
+           regardless of what the Content-Type field actually says. */
+        if ($this->_transferEncoding == 'x-unknown') {
+            return;
+        }
+
+        list($this->_type, $this->_subtype) = explode('/', String::lower($mimetype));
+
+        /* Known types. */
+        $known = array(
+            'text', 'multipart', 'message', 'application', 'audio', 'image',
+            'video', 'model'
+        );
+
+        if (in_array($this->_type, $known)) {
+            /* Set the boundary string for 'multipart/*' parts. */
+            if ($this->_type == 'multipart') {
+                if (!$this->getContentTypeParameter('boundary')) {
+                    $this->setContentTypeParameter('boundary', $this->_generateBoundary());
+                }
+            } else {
+                $this->clearContentTypeParameter('boundary');
+            }
+        } else {
+            $this->_type = 'x-unknown';
+            $this->clearContentTypeParameter('boundary');
+        }
+    }
+
+     /**
+      * Get the full MIME Content-Type of this part.
+      *
+      * @param boolean $charset  Append character set information to the end
+      *                          of the content type if this is a text/* part?
+      *
+      * @return string  The mimetype of this part
+      *                 (ex.: text/plain; charset=us-ascii).
+      */
+     public function getType($charset = false)
+     {
+         if (!isset($this->_type) || !isset($this->_subtype)) {
+             return false;
+         }
+
+         $ptype = $this->getPrimaryType();
+         $type = $ptype . '/' . $this->getSubType();
+         if ($charset && ($ptype == 'text')) {
+             $type .= '; charset=' . $this->getCharset();
+         }
+
+         return $type;
+     }
+
+    /**
+     * If the subtype of a MIME part is unrecognized by an application, the
+     * default type should be used instead (See RFC 2046).  This method
+     * returns the default subtype for a particular primary MIME type.
+     *
+     * @return string  The default MIME type of this part (ex.: text/plain).
+     */
+    public function getDefaultType()
+    {
+        switch ($this->getPrimaryType()) {
+        case 'text':
+            /* RFC 2046 (4.1.4): text parts default to text/plain. */
+            return 'text/plain';
+
+        case 'multipart':
+            /* RFC 2046 (5.1.3): multipart parts default to multipart/mixed. */
+            return 'multipart/mixed';
+
+        default:
+            /* RFC 2046 (4.2, 4.3, 4.4, 4.5.3, 5.2.4): all others default to
+               application/octet-stream. */
+            return 'application/octet-stream';
+        }
+    }
+
+    /**
+     * Get the primary type of this part.
+     *
+     * @return string  The primary MIME type of this part.
+     */
+    public function getPrimaryType()
+    {
+        return $this->_type;
+    }
+
+    /**
+     * Get the subtype of this part.
+     *
+     * @return string  The MIME subtype of this part.
+     */
+    public function getSubType()
+    {
+        return $this->_subtype;
+    }
+
+    /**
+     * Set the character set of this part.
+     *
+     * @param string $charset  The character set of this part.
+     */
+    public function setCharset($charset)
+    {
+        $this->setContentTypeParameter('charset', $charset);
+    }
+
+    /**
+     * Get the character set to use for of this part.  Returns a charset for
+     * all types (not just 'text/*') since we use this charset to determine
+     * how to encode text in MIME headers.
+     *
+     * @return string  The character set of this part.  Returns null if there
+     *                 is no character set.
+     */
+    public function getCharset()
+    {
+        $charset = $this->getContentTypeParameter('charset');
+        return empty($charset) ? null : $charset;
+    }
+
+    /**
+     * Set the description of this part.
+     *
+     * @param string $description  The description of this part.
+     */
+    public function setDescription($description)
+    {
+        $this->_description = $description;
+    }
+
+    /**
+     * Get the description of this part.
+     *
+     * @param boolean $default  If the name parameter doesn't exist, should we
+     *                          use the default name from the description
+     *                          parameter?
+     *
+     * @return string  The description of this part.
+     */
+    public function getDescription($default = false)
+    {
+        $desc = $this->_description;
+
+        if ($default && empty($desc)) {
+            $desc = $this->getName();
+        }
+
+        return $desc;
+    }
+
+    /**
+     * Set the transfer encoding to use for this part.
+     *
+     * @param string $encoding  The transfer encoding to use.
+     */
+    public function setTransferEncoding($encoding)
+    {
+        $known = array('7bit', '8bit', 'binary', 'base64', 'quoted-printable');
+        $encoding = String::lower($encoding);
+
+        if (in_array($encoding, $known)) {
+            $this->_transferEncoding = $encoding;
+        } else {
+            /* RFC 2045: Any entity with unrecognized encoding must be treated
+               as if it has a Content-Type of "application/octet-stream"
+               regardless of what the Content-Type field actually says. */
+            $this->setType('application/octet-stream');
+            $this->_transferEncoding = 'x-unknown';
+        }
+    }
+
+    /**
+     * Add a MIME subpart.
+     *
+     * @param Horde_Mime_Part $mime_part  Add a subpart to the current object.
+     */
+    public function addPart($mime_part)
+    {
+        $this->_parts[] = $mime_part;
+        $this->_reindex = true;
+    }
+
+    /**
+     * Get a list of all MIME subparts.
+     *
+     * @return array  An array of the Horde_Mime_Part subparts.
+     */
+    public function getParts()
+    {
+        return $this->_parts;
+    }
+
+    /**
+     * Retrieve a specific MIME part.
+     *
+     * @param string $id  The MIME ID to get.
+     *
+     * @return Horde_Mime_Part  The part requested or null if the part doesn't
+     *                          exist.
+     */
+    public function getPart($id)
+    {
+        return $this->_partAction($id, 'get');
+    }
+
+    /**
+     * Remove a subpart.
+     *
+     * @param string $id  The MIME ID to delete.
+     *
+     * @param boolean  Success status.
+     */
+    public function removePart($id)
+    {
+        return $this->_partAction($id, 'remove');
+    }
+
+    /**
+     * Alter a current MIME subpart.
+     *
+     * @param string $id                  The MIME ID to alter.
+     * @param Horde_Mime_Part $mime_part  The MIME part to store.
+     *
+     * @param boolean  Success status.
+     */
+    public function alterPart($id, $mime_part)
+    {
+        return $this->_partAction($id, 'alter', $mime_part);
+    }
+
+    /**
+     * Function used to find a specific MIME part by ID and perform an action
+     * on it.
+     *
+     * @param string $id                  The MIME ID.
+     * @param string $action              The action to perform ('get',
+     *                                    'remove', or 'alter').
+     * @param Horde_Mime_Part $mime_part  The object to use for 'alter'.
+     *
+     * @return mixed  See calling functions.
+     */
+    protected function _partAction($id, $action, $mime_part = null)
+    {
+        $this_id = $this->getMimeId();
+
+        /* Need strcmp() because, e.g., '2.0' == '2'. */
+        if (($action == 'get') && (strcmp($id, $this_id) === 0)) {
+            return $this;
+        }
+
+        if ($this->_reindex) {
+            $this->buildMimeIds(is_null($this_id) ? '1' : $this_id);
+        }
+
+        foreach (array_keys($this->_parts) as $val) {
+            $partid = $this->_parts[$val]->getMimeId();
+            if (strcmp($id, $partid) === 0) {
+                switch ($action) {
+                case 'alter':
+                    $mime_part->setMimeId($this->_parts[$val]->getMimeId());
+                    $this->_parts[$val] = $mime_part;
+                    return true;
+
+                case 'get':
+                    return $this->_parts[$val];
+
+                case 'remove':
+                    unset($this->_parts[$val]);
+                    $this->_reindex = true;
+                    return true;
+                }
+            }
+
+            if ((strpos($id, $partid . '.') === 0) ||
+                (strrchr($partid, '.') === '.0')) {
+                return $this->_parts[$val]->_partAction($id, $action, $mime_part);
+            }
+        }
+
+        return ($action == 'get') ? null : false;
+    }
+
+    /**
+     * Add a content type parameter to this part.
+     *
+     * @param string $label  The disposition parameter label.
+     * @param string $data   The disposition parameter data.
+     */
+    public function setContentTypeParameter($label, $data)
+    {
+        $this->_contentTypeParams[$label] = $data;
+    }
+
+    /**
+     * Clears a content type parameter from this part.
+     *
+     * @param string $label  The disposition parameter label.
+     * @param string $data   The disposition parameter data.
+     */
+    public function clearContentTypeParameter($label)
+    {
+        unset($this->_contentTypeParams[$label]);
+    }
+
+    /**
+     * Get a content type parameter from this part.
+     *
+     * @param string $label  The content type parameter label.
+     *
+     * @return string  The data requested.
+     *                 Returns null if $label is not set.
+     */
+    public function getContentTypeParameter($label)
+    {
+        return isset($this->_contentTypeParams[$label])
+            ? $this->_contentTypeParams[$label]
+            : null;
+    }
+
+    /**
+     * Get all parameters from the Content-Type header.
+     *
+     * @return array  An array of all the parameters
+     *                Returns the empty array if no parameters set.
+     */
+    public function getAllContentTypeParameters()
+    {
+        return $this->_contentTypeParams;
+    }
+
+    /**
+     * Sets a new string to use for EOLs.
+     *
+     * @param string $eol  The string to use for EOLs.
+     */
+    public function setEOL($eol)
+    {
+        $this->_eol = $eol;
+    }
+
+    /**
+     * Get the string to use for EOLs.
+     *
+     * @return string  The string to use for EOLs.
+     */
+    public function getEOL()
+    {
+        return $this->_eol;
+    }
+
+    /**
+     * Returns a Horde_Mime_Header object containing all MIME headers needed
+     * for the part.
+     *
+     * @param Horde_Mime_Headers $headers  The Horde_Mime_Headers object to
+     *                                     add the MIME headers to. If not
+     *                                     specified, adds the headers to a
+     *                                     new object.
+     *
+     * @return Horde_Mime_Headers  A Horde_Mime_Headers object.
+     */
+    public function addMimeHeaders($headers = null)
+    {
+        if (is_null($headers)) {
+            $headers = new Horde_Mime_Headers();
+        }
+
+        /* Get the Content-Type itself. */
+        $ptype = $this->getPrimaryType();
+        $c_params = $this->getAllContentTypeParameters();
+        if ($ptype != 'text') {
+            unset($c_params['charset']);
+        }
+        $headers->replaceHeader('Content-Type', $this->getType(), array('params' => $c_params));
+
+        /* Get the description, if any. */
+        if (($descrip = $this->getDescription())) {
+            $headers->replaceHeader('Content-Description', $descrip);
+        }
+
+        /* Per RFC 2046 [4], this MUST appear in the base message headers. */
+        if ($this->_basepart) {
+            $headers->replaceHeader('MIME-Version', '1.0');
+        }
+
+        /* message/* parts require no additional header information. */
+        if ($ptype == 'message') {
+            return $headers;
+        }
+
+        /* Don't show Content-Disposition for multipart messages unless
+           there is a name parameter. */
+        $name = $this->getName();
+        if (($ptype != 'multipart') || !empty($name)) {
+            $headers->replaceHeader('Content-Disposition', $this->getDisposition(), array('params' => (!empty($name) ? array('filename' => $name) : array())));
+        }
+
+        /* Add transfer encoding information. */
+        $headers->replaceHeader('Content-Transfer-Encoding', $this->getTransferEncoding());
+
+        /* Add content ID information. */
+        if (!is_null($this->_contentid)) {
+            $headers->replaceHeader('Content-ID', $this->_contentid);
+        }
+
+        return $headers;
+    }
+
+    /**
+     * Return the entire part in MIME format. Includes headers on request.
+     *
+     * @param boolean $headers  Include the MIME headers?
+     *
+     * @return string  The MIME string.
+     */
+    public function toString($headers = true)
+    {
+        $eol = $this->getEOL();
+        $ptype = $this->getPrimaryType();
+        $text = '';
+
+        if ($headers) {
+            $hdr_ob = $this->addMimeHeaders();
+            $hdr_ob->setEOL($eol);
+            $text = $hdr_ob->toString(array('charset' => $this->getCharset()));
+        }
+
+        /* Any information about a message/* is embedded in the message
+           contents themself. Simply output the contents of the part
+           directly and return. */
+        if ($ptype == 'message') {
+            return $text . $this->_contents;
+        }
+
+        $text .= $this->transferEncode();
+
+        /* Deal with multipart messages. */
+        if ($ptype == 'multipart') {
+            $this->_generateBoundary();
+            $boundary = trim($this->getContentTypeParameter('boundary'), '"');
+            if (!strlen($this->_contents)) {
+                $text .= 'This message is in MIME format.' . $eol;
+            }
+            reset($this->_parts);
+            while (list(,$part) = each($this->_parts)) {
+                $text .= $eol . '--' . $boundary . $eol;
+                $oldEOL = $part->getEOL();
+                $part->setEOL($eol);
+                $text .= $part->toString(true);
+                $part->setEOL($oldEOL);
+            }
+            $text .= $eol . '--' . $boundary . '--' . $eol;
+        }
+
+        return $text;
+    }
+
+    /**
+     * Returns the encoded part in strict RFC 822 & 2045 output - namely, all
+     * newlines end with the canonical <CR><LF> sequence.
+     *
+     * @param boolean $headers  Include the MIME headers?
+     *
+     * @return string  The entire MIME part.
+     */
+    public function toCanonicalString($headers = true)
+    {
+        $string = $this->toString($headers);
+        return $this->replaceEOL($string, self::RFC_EOL);
+    }
+
+    /**
+     * Should we make sure the message is encoded via 7-bit (e.g. to adhere
+     * to mail delivery standards such as RFC 2821)?
+     *
+     * @param boolean $use7bit  Use 7-bit encoding?
+     */
+    public function strict7bit($use7bit)
+    {
+        $this->_encode7bit = $use7bit;
+    }
+
+    /**
+     * Get the transfer encoding for the part based on the user requested
+     * transfer encoding and the current contents of the part.
+     *
+     * @return string  The transfer-encoding of this part.
+     */
+    public function getTransferEncoding()
+    {
+        $encoding = $this->_transferEncoding;
+
+        /* If there are no contents, return whatever the current value of
+           $_transferEncoding is. */
+        if (empty($this->_contents)) {
+            return $encoding;
+        }
+
+        $ptype = $this->getPrimaryType();
+
+        switch ($ptype) {
+        case 'message':
+            /* RFC 2046 [5.2.1] - message/rfc822 messages only allow 7bit,
+               8bit, and binary encodings. If the current encoding is either
+               base64 or q-p, switch it to 8bit instead.
+               RFC 2046 [5.2.2, 5.2.3, 5.2.4] - All other message/* messages
+               only allow 7bit encodings. */
+            $encoding = ($this->getSubType() == 'rfc822') ? '8bit' : '7bit';
+            break;
+
+        case 'text':
+            $eol = $this->getEOL();
+            if (Horde_Mime::is8bit($this->_contents)) {
+                $encoding = ($this->_encode7bit) ? 'quoted-printable' : '8bit';
+            } elseif (preg_match("/(?:" . $eol . "|^)[^" . $eol . "]{999,}(?:" . $eol . "|$)/", $this->_contents)) {
+                /* If the text is longer than 998 characters between
+                 * linebreaks, use quoted-printable encoding to ensure the
+                 * text will not be chopped (i.e. by sendmail if being sent
+                 * as mail text). */
+                $encoding = 'quoted-printable';
+            }
+            break;
+
+        default:
+            if (Horde_Mime::is8bit($this->_contents)) {
+                $encoding = ($this->_encode7bit) ? 'base64' : '8bit';
+            }
+            break;
+        }
+
+        /* Need to do one last check for binary data if encoding is 7bit or
+         * 8bit.  If the message contains a NULL character at all, the message
+         * MUST be in binary format. RFC 2046 [2.7, 2.8, 2.9]. Q-P and base64
+         * can handle binary data fine so no need to switch those encodings. */
+        if (in_array($encoding, array('8bit', '7bit')) &&
+            preg_match('/\x00/', $this->_contents)) {
+            $encoding = ($this->_encode7bit) ? 'base64' : 'binary';
+        }
+
+        return $encoding;
+    }
+
+    /**
+     * Retrieves the current encoding of the contents in the object.
+     *
+     * @return string  The current encoding.
+     */
+    public function getCurrentEncoding()
+    {
+        return empty($this->_flags['currentEncoding'])
+            ? $this->_transferEncoding
+            : $this->_flags['currentEncoding'];
+    }
+
+    /**
+     * Encodes the contents with the part's transfer encoding.
+     *
+     * @return string  The encoded text.
+     */
+    public function transferEncode()
+    {
+        $encoding = $this->getTransferEncoding();
+        $eol = $this->getEOL();
+
+        /* Set the 'lastTransferEncode' flag so that transferEncodeContents()
+           can save a call to getTransferEncoding(). */
+        $this->_flags['lastTransferEncode'] = $encoding;
+
+        /* If contents are empty, or contents are already encoded to the
+           correct encoding, return now. */
+        if (!strlen($this->_contents) ||
+            ($encoding == $this->_flags['currentEncoding'])) {
+            return $this->_contents;
+        }
+
+        switch ($encoding) {
+        /* Base64 Encoding: See RFC 2045, section 6.8 */
+        case 'base64':
+            /* Keeping these two lines separate seems to use much less
+               memory than combining them (as of PHP 4.3). */
+            $encoded_contents = base64_encode($this->_contents);
+            return chunk_split($encoded_contents, 76, $eol);
+
+        /* Quoted-Printable Encoding: See RFC 2045, section 6.7 */
+        case 'quoted-printable':
+            $output = Horde_Mime::quotedPrintableEncode($this->_contents, $eol);
+            if (($eollength = String::length($eol)) &&
+                (substr($output, $eollength * -1) == $eol)) {
+                return substr($output, 0, $eollength * -1);
+            }
+            return $output;
+
+        default:
+            return $this->replaceEOL($this->_contents);
+        }
+    }
+
+    /**
+     * Decodes the contents of the part to either a 7bit or 8bit encoding.
+     *
+     * @return string  The decoded text.
+     *                 Returns the empty string if there is no text to decode.
+     */
+    public function transferDecode()
+    {
+        $encoding = $this->getCurrentEncoding();
+
+        /* If the contents are empty, return now. */
+        if (!strlen($this->_contents)) {
+            $this->_flags['lastTransferDecode'] = $encoding;
+            return $this->_contents;
+        }
+
+        switch ($encoding) {
+        case 'base64':
+            $this->_flags['lastTransferDecode'] = '8bit';
+            return base64_decode($this->_contents);
+
+        case 'quoted-printable':
+            $message = preg_replace("/=\r?\n/", '', $this->_contents);
+            $message = quoted_printable_decode($this->replaceEOL($message));
+            $this->_flags['lastTransferDecode'] = (Horde_Mime::is8bit($message)) ? '8bit' : '7bit';
+            return $message;
+
+        /* Support for uuencoded encoding - although not required by RFCs,
+           some mailers may still encode this way. */
+        case 'uuencode':
+        case 'x-uuencode':
+        case 'x-uue':
+            $this->_flags['lastTransferDecode'] = '8bit';
+            return convert_uuencode($this->_contents);
+
+        default:
+            if (isset($this->_flags['lastTransferDecode']) &&
+                ($this->_flags['lastTransferDecode'] != $encoding)) {
+                $message = $this->replaceEOL($this->_contents);
+            } else {
+                $message = $this->_contents;
+            }
+            $this->_flags['lastTransferDecode'] = $encoding;
+            return $message;
+        }
+    }
+
+    /**
+     * Split the contents of the current Part into its respective subparts,
+     * if it is multipart MIME encoding.
+     *
+     * The boundary Content-Type parameter must be set for this function to
+     * work correctly.
+     *
+     * @return boolean  True if the contents were successfully split.
+     *                  False if any error occurred.
+     */
+    public function splitContents()
+    {
+        if ((!($boundary = $this->getContentTypeParameter('boundary'))) ||
+            !strlen($this->_contents)) {
+            return false;
+        }
+
+        $eol = $this->getEOL();
+        $retvalue = false;
+
+        $boundary = '--' . $boundary;
+        if (substr($this->_contents, 0, strlen($boundary)) == $boundary) {
+            $pos1 = 0;
+        } else {
+            $pos1 = strpos($this->_contents, $eol . $boundary);
+            if ($pos1 === false) {
+                return false;
+            }
+        }
+
+        $pos1 = strpos($this->_contents, $eol, $pos1 + 1);
+        if ($pos1 === false) {
+            return false;
+        }
+        $pos1 += strlen($eol);
+
+        reset($this->_parts);
+        $part_ptr = key($this->_parts);
+
+        while ($pos2 = strpos($this->_contents, $eol . $boundary, $pos1)) {
+            $this->_parts[$part_ptr]->setContents(substr($this->_contents, $pos1, $pos2 - $pos1));
+            $this->_parts[$part_ptr]->splitContents();
+            next($this->_parts);
+            $part_ptr = key($this->_parts);
+            if (is_null($part_ptr)) {
+                return false;
+            }
+            $pos1 = strpos($this->_contents, $eol, $pos2 + 1);
+            if ($pos1 === false) {
+                return true;
+            }
+            $pos1 += strlen($eol);
+        }
+
+        return true;
+    }
+
+    /**
+     * Replace newlines in this part's contents with those specified by either
+     * the given newline sequence or the part's current EOL setting.
+     *
+     * @param string $text  The text to replace.
+     * @param string $eol   The EOL sequence to use. If not present, uses the
+     *                      part's current EOL setting.
+     *
+     * @return string  The text with the newlines replaced by the desired
+     *                 newline sequence.
+     */
+    public function replaceEOL($text, $eol = null)
+    {
+        if (is_null($eol)) {
+            $eol = $this->getEOL();
+        }
+        return preg_replace("/\r?\n/", $eol, $text);
+    }
+
+    /**
+     * Determine the size of this MIME part and its child members.
+     *
+     * @return integer  Size of the part, in bytes.
+     */
+    public function getBytes()
+    {
+        $bytes = 0;
+
+        if (empty($this->_flags['contentsSet']) && $this->_bytes) {
+            $bytes = $this->_bytes;
+        } elseif ($this->getPrimaryType() == 'multipart') {
+            reset($this->_parts);
+            while (list(,$part) = each($this->_parts)) {
+                /* Skip multipart entries (since this may result in double
+                   counting). */
+                if ($part->getPrimaryType() != 'multipart') {
+                    $bytes += $part->getBytes();
+                }
+            }
+        } else {
+            $bytes = ($this->getPrimaryType() == 'text')
+                ? String::length($this->_contents, $this->getCharset())
+                : strlen($this->_contents);
+        }
+
+        return $bytes;
+    }
+
+    /**
+     * Explicitly set the size (in bytes) of this part. This value will only
+     * be returned (via getBytes()) if there are no contents currently set.
+     * This function is useful for setting the size of the part when the
+     * contents of the part are not fully loaded (i.e. creating a
+     * Horde_Mime_Part object from IMAP header information without loading the
+     * data of the part).
+     *
+     * @param integer $bytes  The size of this part in bytes.
+     */
+    public function setBytes($bytes)
+    {
+        $this->_bytes = $bytes;
+    }
+
+    /**
+     * Output the size of this MIME part in KB.
+     *
+     * @return string  Size of the part, in string format.
+     */
+    public function getSize()
+    {
+        $bytes = $this->getBytes();
+        if (empty($bytes)) {
+            return $bytes;
+        }
+
+        $localeinfo = NLS::getLocaleInfo();
+        return number_format($bytes / 1024, 2, $localeinfo['decimal_point'], $localeinfo['thousands_sep']);
+     }
+
+    /**
+     * Sets the Content-ID header for this part.
+     *
+     * @param string $cid  Use this CID (if not already set).  Else, generate
+     *                     a random CID.
+     */
+    public function setContentId($cid = null)
+    {
+        if (is_null($this->_contentid)) {
+            $this->_contentid = (is_null($cid)) ? (Horde_Mime::generateRandomId() . '@' . $_SERVER['SERVER_NAME']) : $cid;
+        }
+        return $this->_contentid;
+    }
+
+    /**
+     * Returns the Content-ID for this part.
+     *
+     * @return string  The Content-ID for this part.
+     */
+    public function getContentId()
+    {
+        return $this->_contentid;
+    }
+
+    /**
+     * Alter the MIME ID of this part.
+     *
+     * @param string $mimeid  The MIME ID.
+     */
+    public function setMimeId($mimeid)
+    {
+        $this->_mimeid = $mimeid;
+    }
+
+    /**
+     * Returns the MIME ID of this part.
+     *
+     * @return string  The MIME ID.
+     */
+    public function getMimeId()
+    {
+        return $this->_mimeid;
+    }
+
+    /**
+     * Build the MIME IDs for this part and all subparts.
+     *
+     * @param string $id       The ID of this part.
+     * @param boolean $rfc822  Is this a message/rfc822 part?
+     */
+    public function buildMimeIds($id = null, $rfc822 = false)
+    {
+        if (is_null($id)) {
+            $rfc822 = true;
+            $id = '';
+        }
+
+        if ($rfc822) {
+            if (empty($this->_parts)) {
+                $this->setMimeId($id . '1');
+            } else {
+                if (empty($id) && ($this->getType() == 'message/rfc822')) {
+                    $this->setMimeId('1');
+                    $id = '1.';
+                } else {
+                    $this->setMimeId($id . '0');
+                }
+                $i = 1;
+                foreach (array_keys($this->_parts) as $val) {
+                    $this->_parts[$val]->buildMimeIds($id . $i++);
+                }
+            }
+        } else {
+            $this->setMimeId($id);
+            $id .= '.';
+
+            if ($this->getType() == 'message/rfc822') {
+                if (count($this->_parts)) {
+                    reset($this->_parts);
+                    $this->_parts[key($this->_parts)]->buildMimeIds($id, true);
+                }
+            } elseif (!empty($this->_parts)) {
+                $i = 1;
+                foreach (array_keys($this->_parts) as $val) {
+                    $this->_parts[$val]->buildMimeIds($id . $i++);
+                }
+            }
+        }
+
+        $this->_reindex = false;
+    }
+
+    /**
+     * Generate the unique boundary string (if not already done).
+     *
+     * @return string  The boundary string.
+     */
+    protected function _generateBoundary()
+    {
+        if (is_null($this->_boundary)) {
+            $this->_boundary = '=_' . Horde_Mime::generateRandomId();
+        }
+        return $this->_boundary;
+    }
+
+    /**
+     * Returns a mapping of all MIME IDs to their content-types.
+     *
+     * @param boolean $sort  Sort by MIME ID?
+     *
+     * @return array  Keys: MIME ID; values: content type.
+     */
+    public function contentTypeMap($sort = true)
+    {
+        $map = array($this->getMimeId() => $this->getType());
+        foreach ($this->_parts as $val) {
+            $map += $val->contentTypeMap(false);
+        }
+
+        if ($sort) {
+            uksort($map, 'strnatcasecmp');
+        }
+
+        return $map;
+    }
+
+    /**
+     * Is this the base MIME part?
+     *
+     * @param boolean $base  True if this is the base MIME part.
+     */
+    public function isBasePart($base)
+    {
+        $this->_basepart = $base;
+    }
+
+    /**
+     * Sends this message.
+     *
+     * @param string $email                 The address list to send to.
+     * @param Horde_Mime_Headers $headers   The Horde_Mime_Headers object
+     *                                      holding this message's headers.
+     * @param string $driver                The Mail:: driver to use.
+     * @param array $params                 Any parameters necessary for the
+     *                                      Mail driver.
+     *
+     * @return mixed  True on success, PEAR_Error on error.
+     */
+    public function send($email, $headers, $driver, $params = array())
+    {
+        require_once 'Mail.php';
+        $mailer = Mail::factory($driver, $params);
+
+        $old_basepart = $this->_basepart;
+        $this->_basepart = true;
+
+        /* Add MIME Headers if they don't already exist. */
+        if (!$headers->getValue('MIME-Version')) {
+            $headers = $this->addMimeHeaders($headers);
+        }
+        $headerArray = $headers->toArray(array('charset' => $this->getCharset()));
+
+        /* Does the SMTP backend support 8BITMIME (RFC 1652) or
+         * BINARYMIME (RFC 3030) extensions? Requires PEAR's Mail package
+         * version 1.2+ and Net_SMTP version 1.3+. */
+        if (($driver == 'smtp') && method_exists($mailer, 'getSMTPObject')) {
+            $net_smtp = $mailer->getSMTPObject();
+            if (!is_a($net_smtp, 'PEAR_Error') &&
+                method_exists($net_smtp, 'getServiceExtensions')) {
+                $smtp_ext = $net_smtp->getServiceExtensions();
+                $this->strict7bit(false);
+                $encoding = $this->getTransferEncoding();
+                if (($encoding == '8bit') &&
+                    isset($smtp_ext['8BITMIME'])) {
+                    $mailer->addServiceExtensionParameter('BODY', '8BITMIME');
+                } elseif (($encoding == 'binary') &&
+                          isset($smtp_ext['BINARYMIME'])) {
+                    $mailer->addServiceExtensionParameter('BODY', 'BINARYMIME');
+                } else {
+                    $this->strict7bit(true);
+                    $encoding = $this->getTransferEncoding();
+                }
+                $headers->replaceHeader('Content-Transfer-Encoding', $encoding);
+            }
+        }
+
+        /* Make sure the message has a trailing newline. */
+        $msg = $this->toString(false);
+        if (substr($msg, -1) != "\n") {
+            $msg .= "\n";
+        }
+
+        $result = $mailer->send(Horde_Mime::encodeAddress($email), $headerArray, $msg);
+
+        $this->_basepart = $old_basepart;
+
+        if (is_a($result, 'PEAR_Error') && ($driver == 'sendmail')) {
+            $error = Horde_Mime_Mail::sendmailError($result->getCode());
+            if (is_null($error)) {
+                $error = $result;
+                $userinfo = null;
+            } else {
+                $userinfo = $result->toString();
+            }
+            return PEAR::raiseError($error, null, null, null, $userinfo);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Finds the main "body" text part (if any) in a message.
+     * "Body" data is the first text part under this part.
+     *
+     * @param string $subtype  Specifically search for this subtype.
+     *
+     * @return mixed  The MIME ID of the main body part, or null if a body
+                      part is not found.
+     */
+    public function findBody($subtype = null)
+    {
+        foreach ($this->contentTypeMap() as $mime_id => $mime_type) {
+            if ((strpos($mime_type, 'text/') === 0) &&
+                (intval($mime_id) == 1) &&
+                (is_null($subtype) || (substr($mime_type, 5) == $subtype))) {
+                return $mime_id;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Parse an array of MIME structure information into a Horde_Mime_Part
+     * object.
+     * This function can be called statically via:
+     *    $mime_part = Horde_Mime_Part::parseStructure();
+     *
+     * @param array $structure  An array of structure information in the
+     *                          following format:
+     * <pre>
+     * MANDATORY:
+     *   'type' - (string) The MIME type
+     *   'subtype' - (string) The MIME subtype
+     *
+     * The array MAY contain the following information:
+     *   'contents' - (string) The contents of the part.
+     *   'disposition' - (string) The disposition type of the part (e.g.
+     *                   'attachment', 'inline').
+     *   'dparameters' - (array) Attribute/value pairs from the part's
+     *                   Content-Disposition header.
+     *   'language' - (array) A list of body language values.
+     *   'location' - (string) The body content URI.
+     *
+     * Depending on the MIME type of the part, the array will also contain
+     * further information. If labeled as [OPTIONAL], the array MAY
+     * contain this information, but only if 'noext' is false and the
+     * server returned the requested information. Else, the value is not
+     * set.
+     *
+     * multipart/* parts:
+     * ==================
+     * 'parts' - (array) An array of subparts (follows the same format as
+     *           the base structure array).
+     * 'parameters' - [OPTIONAL] (array) Attribute/value pairs from the
+     *                part's Content-Type header.
+     *
+     * All other parts:
+     * ================
+     * 'parameters' - (array) Attribute/value pairs from the part's
+     *                Content-Type header.
+     * 'id' - (string) The part's Content-ID value.
+     * 'description' - (string) The part's Content-Description value.
+     * 'encoding' - (string) The part's Content-Transfer-Encoding value.
+     * 'size' - (integer) - The part's size in bytes.
+     * 'envelope' - [ONLY message/rfc822] (array) See 'envelope' response.
+     * 'structure' - [ONLY message/rfc822] (array) See 'structure'
+     *               response.
+     * 'lines' - [ONLY message/rfc822 and text/*] (integer) The size of
+     *           the body in text lines.
+     * 'md5' - [OPTIONAL] (string) The part's MD5 value.
+     * </pre>
+     *
+     * @return object  A Horde_Mime_Part object.
+     */
+    static public function parseStructure($structure)
+    {
+        $ob = self::_parseStructure($structure);
+        $ob->buildMimeIds();
+        return $ob;
+    }
+
+    /**
+     * Parse a subpart of a MIME message into a Horde_Mime_Part object.
+     *
+     * @param array $data  Structure information in the format described
+     *                     in parseStructure().
+     *
+     * @return Horde_Mime_Part  The generated object.
+     */
+    static protected function _parseStructure($data)
+    {
+        $ob = new Horde_Mime_Part();
+        $ob->setType($data['type'] . '/' . $data['subtype']);
+
+        if (isset($data['encoding'])) {
+            $ob->setTransferEncoding($data['encoding']);
+        }
+
+        if (isset($data['contents'])) {
+            $ob->setContents($data['contents'], $ob->getTransferEncoding());
+            $ob->transferDecodeContents();
+        }
+
+        if (isset($data['disposition'])) {
+            $ob->setDisposition($data['disposition']);
+            if (!empty($data['dparameters'])) {
+                foreach ($data['dparameters'] as $key => $val) {
+                    /* Disposition parameters are supposed to be encoded via
+                     * RFC 2231, but many mailers do RFC 2045 encoding
+                     * instead. */
+                    // @todo: RFC 2231 decoding
+                    $ob->setDispositionParameter($key, Horde_Mime::decode($val));
+                }
+            }
+        }
+
+        if (isset($data['size'])) {
+            $ob->setBytes($data['size']);
+        }
+
+        if (isset($data['id'])) {
+            $ob->setContentId($data['id']);
+        }
+
+        if (!empty($data['parameters'])) {
+            foreach ($data['parameters'] as $key => $val) {
+                /* Content-type parameters are supposed to be encoded via RFC
+                 * 2231, but many mailers do RFC 2045 encoding instead. */
+                // @todo: RFC 2231 decoding
+                $ob->setContentTypeParameter($key, Horde_Mime::decode($val));
+            }
+        }
+
+        /* Set the default character set. */
+        if (($data['subtype'] == 'text') &&
+            (String::lower($ob->getCharset()) == 'us-ascii') &&
+            isset($GLOBALS['mime_structure']['default_charset'])) {
+            /* @todo - switch to using static variable for this. */
+            //$ob->setCharset($GLOBALS['mime_structure']['default_charset']);
+        }
+
+        if (isset($data['description'])) {
+            $ob->setDescription(Horde_Mime::decode($data['description']));
+        }
+
+        /* Set the name. */
+        if (!$ob->getName()) {
+            $ob->setName($ob->getDispositionParameter('filename'));
+        }
+
+        // @todo Handle language, location, md5, lines, envelope
+
+        /* Add multipart parts. */
+        if (!empty($data['parts'])) {
+            reset($data['parts']);
+            while (list(,$val) = each($data['parts'])) {
+                $ob->addPart(self::_parseStructure($val));
+            }
+        } elseif (!empty($data['structure'])) {
+            $ob->addPart(self::_parseStructure($data['structure']));
+        }
+
+        return $ob;
+    }
+
+    /**
+     * Attempts to build a Horde_Mime_Part object from message text.
+     * This function can be called statically via:
+     *    $mime_part = Horde_Mime_Part::parseMessage();
+     *
+     * @param string $text  The text of the MIME message.
+     *
+     * @return Horde_Mime_Part  A Horde_Mime_Part object, or false on error.
+     */
+    static public function parseMessage($text, $options = array())
+    {
+        /* Set up the options for the mimeDecode class. */
+        $decode_args = array(
+            'include_bodies' => true,
+            'decode_bodies' => false,
+            'decode_headers' => false
+        );
+
+        require_once 'Mail/mimeDecode.php';
+        $mimeDecode = new Mail_mimeDecode($text, Horde_Mime_Part::EOL);
+        if (!($ob = $mimeDecode->decode($decode_args))) {
+            return false;
+        }
+
+        return self::parseStructure(self::_convertMimeDecodeData($ob), $options);
+    }
+
+    /**
+     * Convert the output from Mail_mimeDecode::decode() into a structure that
+     * parse() can handle.
+     *
+     * @param stdClass $ob  The output from Mail_mimeDecode::decode().
+     *
+     * @return array  An array of structure information.
+     */
+    static protected function _convertMimeDecodeData($ob)
+    {
+        /* Primary content-type. */
+        if (isset($ob->ctype_primary)) {
+            $part = array(
+                'type' => strtolower($ob->ctype_primary),
+                'subtype' => isset($ob->ctype_secondary) ? strtolower($ob->ctype_secondary) : 'x-unknown'
+            );
+        } else {
+            $part = array(
+                'type' => 'application',
+                'subtype' => 'octet-stream'
+            );
+        }
+
+        /* Content transfer encoding. */
+        if (isset($ob->headers['content-transfer-encoding'])) {
+            $part['encoding'] = strtolower($ob->headers['content-transfer-encoding']);
+        }
+
+        /* Content-type and Disposition parameters. */
+        $param_types = array(
+            'ctype_parameters' => 'parameters',
+            'd_parameters' => 'dparameters'
+        );
+
+        foreach ($param_types as $param_key => $param_value) {
+            if (isset($ob->$param_key)) {
+                $part[$param_value] = array();
+                foreach ($ob->$param_key as $key => $val) {
+                    $part[$param_value][strtolower($key)] = $val;
+                }
+            }
+        }
+
+        /* Content-Description. */
+        if (isset($ob->headers['content-description'])) {
+            $part['description'] = $ob->headers['content-description'];
+        }
+
+        /* Content-Disposition. */
+        if (isset($ob->headers['content-disposition'])) {
+            $hdr = $ob->headers['content-disposition'];
+            $pos = strpos($hdr, ';');
+            if ($pos !== false) {
+                $hdr = substr($hdr, 0, $pos);
+            }
+            $part['disposition'] = strtolower($hdr);
+        }
+
+        /* Content-ID. */
+        if (isset($ob->headers['content-id'])) {
+            $part['id'] = $ob->headers['content-id'];
+        }
+
+        /* Get file size (if 'body' text is set). */
+        if (isset($ob->body)) {
+            $part['contents'] = $ob->body;
+            if (($part['type'] != 'message') &&
+                ($part['subtype'] != 'rfc822')) {
+                /* Mail_mimeDecode puts an extra linebreak at the end of body
+                 * text. */
+                $size = strlen(str_replace(array("\r\n", "\n"), array("\n", "\r\n"), $ob->body)) - 2;
+                $part['size'] = ($size < 0) ? 0 : $size;
+            }
+        }
+
+        /* Process parts also. */
+        if (isset($ob->parts)) {
+            $part['parts'] = array();
+            reset($ob->parts);
+            while (list($key,) = each($ob->parts)) {
+                $part['parts'][] = self::_convertMimeDecodeData($ob->parts[$key]);
+            }
+        }
+
+        return $part;
+    }
+
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer.php b/framework/Mime/lib/Horde/Mime/Viewer.php
new file mode 100644 (file)
index 0000000..9397a41
--- /dev/null
@@ -0,0 +1,289 @@
+<?php
+/**
+ * The Horde_Mime_Viewer:: class provides an abstracted interface to render
+ * MIME data into various formats.  It depends on both a set of
+ * Horde_Mime_Viewer_* drivers which handle the actual rendering, and a
+ * configuration file to map MIME types to drivers.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime
+ */
+class Horde_Mime_Viewer
+{
+    /**
+     * The config array. This array is shared between all instances of
+     * Horde_Mime_Viewer.
+     *
+     * @var array
+     */
+    static protected $_config = array();
+
+    /**
+     * The driver cache array. This array is shared between all instances of
+     * Horde_Mime_Viewer.
+     *
+     * @var array
+     */
+    static protected $_drivercache = array();
+
+    /**
+     * Attempts to return a concrete Horde_Mime_Viewer_* object based on the
+     * MIME type.
+     *
+     * @param Horde_Mime_Part &$mime_part  Reference to an object with the
+     *                                     information to be rendered.
+     * @param string $mime_type            The MIME type (overrides the value
+     *                                     in the MIME part).
+     *
+     * @return Horde_Mime_Viewer  The Horde_Mime_Viewer object, or false on
+     *                            error.
+     */
+    static final public function factory(&$mime_part, $mime_type = null)
+    {
+        if (is_null($mime_type)) {
+            $mime_type = $mime_part->getType();
+        }
+
+        /* Spawn the relevant driver, and return it (or false on failure). */
+        if (($ob = self::_getDriver($mime_type, $GLOBALS['registry']->getApp())) &&
+            self::_resolveDriver($ob['driver'], $ob['app']) &&
+            class_exists($ob['class'])) {
+            $conf = array_merge(self::$_config['mime_drivers'][$ob['app']][$ob['driver']], array('_driver' => $ob['driver']));
+            return new $ob['class']($mime_part, $conf);
+        }
+
+        return false;
+    }
+
+    /**
+     * Given a MIME type, this function will return an appropriate icon.
+     *
+     * @param string $mime_type  The MIME type that we need an icon for.
+     *
+     * @return string  The URL to the appropriate icon.
+     */
+    static final public function getIcon($mime_type)
+    {
+        $app = $GLOBALS['registry']->getApp();
+        $ob = self::_getIcon($mime_type, $app);
+
+        if (is_null($ob)) {
+            if ($app == 'horde') {
+                return null;
+            }
+
+            $obHorde = self::_getIcon($mime_type, 'horde');
+            return is_null($obHorde) ? null : $obHorde['url'];
+        } elseif (($ob['match'] !== 0) && ($app != 'horde')) {
+            $obHorde = self::_getIcon($mime_type, 'horde');
+            if (!is_null($ob['match']) &&
+                ($obHorde['match'] <= $ob['match'])) {
+                return $obHorde['url'];
+            }
+        }
+
+        return $ob['url'];
+    }
+
+    /**
+     * Given a MIME type and an app name, determine which driver can best
+     * handle the data.
+     *
+     * @param string $mime_type  MIME type to resolve.
+     * @param string $app        App in which to search for the driver.
+     *
+     * @return mixed  Returns false if driver could not be found. Otherwise,
+     *                an array with the following elements:
+     * <pre>
+     * 'app' - (string) The app containing the driver (e.g. 'horde')
+     * 'driver' - (string) Name of driver (e.g. 'enscript')
+     * 'exact' - (boolean) Was the driver and exact match?
+     * </pre>
+     */
+    static final protected function _getDriver($mime_type, $app = 'horde')
+    {
+        $sig = $mime_type . '|' . $app;
+        if (isset(self::$_drivercache[$sig])) {
+            return self::$_drivercache[$sig];
+        }
+
+        /* Make sure 'horde' mime_drivers config is loaded. */
+        if (empty(self::$_config['mime_drivers']['horde'])) {
+            $res = Horde::loadConfiguration('mime_drivers.php', array('mime_drivers', 'mime_drivers_map'), 'horde');
+            if (is_a($res, 'PEAR_Error')) {
+                return false;
+            }
+            self::$_config = $res;
+        }
+
+        /* Make sure app's' mime_drivers config is loaded. */
+        if (($app != 'horde') && empty(self::$_config['mime_drivers'][$app])) {
+            $res = Horde::loadConfiguration('mime_drivers.php', array('mime_drivers', 'mime_drivers_map'), $app);
+            if (is_a($res, 'PEAR_Error')) {
+                return false;
+            }
+
+            require_once 'Horde/Array.php';
+            self::$_config = Horde_Array::array_merge_recursive_overwrite(self::$_config, $res);
+        }
+
+        $driver = '';
+        $exact = false;
+
+        list($primary_type,) = explode('/', $mime_type, 2);
+        $allSub = $primary_type . '/*';
+
+        /* If the app doesn't exist in $mime_drivers_map, check for Horde-wide
+         * viewers. */
+        if (!isset(self::$_config['mime_drivers_map'][$app]) &&
+            ($app != 'horde')) {
+            return self::_getDriver($mime_type, 'horde');
+        }
+
+        $dr = self::$_config['mime_drivers'][$app];
+        $map = self::$_config['mime_drivers_map'][$app];
+
+        /* If an override exists for this MIME type, then use that */
+        if (isset($map['overrides'][$mime_type])) {
+            $driver = $map['overrides'][$mime_type];
+            $exact = true;
+        } elseif (isset($map['overrides'][$allSub])) {
+            $driver = $map['overrides'][$allSub];
+            $exact = true;
+        } elseif (isset($map['registered'])) {
+            /* Iterate through the list of registered drivers, and see if
+             * this MIME type exists in the MIME types that they claim to
+             * handle. If the driver handles it, then assign it as the
+             * rendering driver. If we find a generic handler, keep iterating
+             * to see if we can find a specific handler. */
+            foreach ($map['registered'] as $val) {
+                if (in_array($mime_type, $dr[$val]['handles'])) {
+                    $driver = $val;
+                    $exact = true;
+                    break;
+                } elseif (in_array($allSub, $dr[$val]['handles'])) {
+                    $driver = $val;
+                }
+            }
+        }
+
+        /* If this is an application specific app, and an exact match was
+           not found, search for a Horde-wide specific driver. Only use the
+           Horde-specific driver if it is NOT the 'default' driver AND the
+           Horde driver is an exact match. */
+        if (!$exact && ($app != 'horde')) {
+            $ob = self::_getDriver($mime_type, 'horde');
+            if (empty($driver) ||
+                (($ob['driver'] != 'default') && $ob['exact'])) {
+                $driver = $ob['driver'];
+                $app = 'horde';
+            }
+        }
+
+        /* If the 'default' driver exists in this app, fall back to that. */
+        if (empty($driver) && self::_resolveDriver('default', $app)) {
+            $driver = 'default';
+        }
+
+        if (empty($driver)) {
+            return false;
+        }
+
+        self::$_drivercache[$sig] = array(
+            'app' => $app,
+            'class' => (($app == 'horde') ? '' : $app . '_') . 'Horde_Mime_Viewer_' . $driver,
+            'driver' => $driver,
+            'exact' => $exact,
+        );
+
+        return self::$_drivercache[$sig];
+    }
+
+    /**
+     * Given a driver and an application, attempts to load the library file.
+     *
+     * @param string $driver  Driver name.
+     * @param string $app     Application name.
+     *
+     * @return boolean  True if library file was loaded.
+     */
+    static final protected function _resolveDriver($driver = 'default',
+                                                   $app = 'horde')
+    {
+        $file = ($app == 'horde')
+            ? dirname(__FILE__) . '/Viewer/' . $driver . '.php'
+            : $GLOBALS['registry']->applications[$app]['fileroot'] . '/lib/Mime/Viewer/' . $driver . '.php';
+
+        require_once dirname(__FILE__) . '/Viewer/Driver.php';
+
+        $old_error = error_reporting(0);
+        $ret = require_once $file;
+        error_reporting($old_error);
+
+        return $ret;
+    }
+
+    /**
+     * Given an input MIME type and app, this function returns the URL of an
+     * icon that can be associated with it
+     *
+     * @param string $mime_type  MIME type to get the icon for.
+     *
+     * @return mixed  Null if not found, or an array with the following keys:
+     * <pre>
+     * 'exact' - (integer) How exact the match is.
+     *           0 => 'exact', 1 => 'primary', 2 => 'driver',
+     *           3 => 'default', or null.
+     * 'url' - (string) URL to an icon, or null if none could be found.
+     * </pre>
+     */
+    static final protected function _getIcon($mime_type, $app = 'horde')
+    {
+        if (!($ob = self::_getDriver($mime_type, $app))) {
+            return null;
+        }
+        $driver = $ob['driver'];
+
+        list($primary_type,) = explode('/', $mime_type, 2);
+        $allSub = $primary_type . '/*';
+        $ret = null;
+
+        /* If the app doesn't exist in $mime_drivers, return now. */
+        if (!isset(self::$_config['mime_drivers'][$app])) {
+            return null;
+        }
+
+        $dr = self::$_config['mime_drivers'][$app];
+
+        /* If a specific icon for this driver and mimetype is defined,
+           then use that. */
+        if (isset($dr[$driver]['icons'])) {
+            $icondr = $dr[$driver]['icons'];
+            $iconList = array($mime_type => 0, $allSub => 1, 'default' => 2);
+            foreach ($iconList as $key => $val) {
+                if (isset($icondr[$key])) {
+                    $ret = array('match' => $val, 'url' => $icondr[$key]);
+                    break;
+                }
+            }
+        }
+
+        /* Try to use a default icon if none already obtained. */
+        if (is_null($ret['url']) &&
+            isset($dr['default']['icons']['default'])) {
+            $ret = array('match' => 3, 'url' => $dr['default']['icons']['default']);
+        }
+
+        if (!is_null($ret)) {
+            $ret['url'] = $GLOBALS['registry']->getImageDir($app) . '/mime/' . $ret['url'];
+        }
+
+        return $ret;
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/Driver.php b/framework/Mime/lib/Horde/Mime/Viewer/Driver.php
new file mode 100644 (file)
index 0000000..00de55b
--- /dev/null
@@ -0,0 +1,280 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_Driver:: class provides the API for specific viewer
+ * drivers to extend.
+ *
+ * Copyright 2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime
+ */
+class Horde_Mime_Viewer_Driver
+{
+    /**
+     * Viewer configuration.
+     *
+     * @var array
+     */
+    protected $_conf = array();
+
+    /**
+     * The Horde_Mime_Part object to render.
+     *
+     * @var Horde_Mime_Part
+     */
+    protected $_mimepart = null;
+
+    /**
+     * Viewer parameters.
+     *
+     * @var array
+     */
+    protected $_params = array();
+
+    /**
+     * This driver's capabilities.
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => false,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Constructor.
+     *
+     * @param Horde_Mime_Part &$mime_part  Reference to an object with the
+     *                                     information to be rendered.
+     * @param array $conf                  Configuration specific to the
+     *                                     driver.
+     */
+    function __construct(&$mime_part, $conf = array())
+    {
+        $this->_mimepart = $mime_part;
+        $this->_conf = $conf;
+    }
+
+    /**
+     * Sets the Horde_Mime_Part object for the class.
+     *
+     * @param Horde_Mime_Part &$mime_part  Reference to an object with the
+     *                                     information to be rendered.
+     */
+    public function setMIMEPart(&$mime_part)
+    {
+        $this->_mimepart = $mime_part;
+    }
+
+    /**
+     * Set parameters for use with this object.
+     *
+     * @param array $params  An array of params to add to the internal
+     *                       params list.
+     */
+    public function setParams($params = array())
+    {
+        $this->_params = array_merge($this->_params, $params);
+    }
+
+    /**
+     * Return the rendered version of the Horde_Mime_Part object.
+     *
+     * @param string $mode  The mode:
+     * <pre>
+     * 'full' - A full representation of the MIME part, for use in a view
+     *          where the output to the browser can be set to the value
+     *          returned in 'type'. This mode should only return a single
+     *          MIME ID entry for viewing and should not return any status
+     *          information.
+     * 'inline' - A representation of the MIME part that can be viewed inline
+     *            on a text/html page that may contain other HTML elements.
+     * 'info' - A representation of the MIME part that can be viewed inline
+     *          on an text/html page that may contain other HTML elements.
+     *          This view is not a full view, but rather a condensed view of
+     *          the contents of the MIME part. This view is intended to be
+     *          displayed to the user with the intention that this MIME part's
+     *          subparts may also independently be viewed inline.
+     * </pre>
+     *
+     * @return array  An array. The keys are the MIME parts that were handled
+     *                by the driver. The values are either null (which
+     *                indicates the driver is recommending that this
+     *                particular MIME ID should not be displayed) or an array
+     *                with the following keys:
+     * <pre>
+     * 'data' - (string) The rendered data.
+     * 'status' - (array) An array of status information to be displayed to
+     *            the user.  Consists of arrays with the following keys:
+     *            'class' - (string) The class to use for display.
+     *            'id' - (string) The DOM ID to use for the status item.
+     *            'img' - (string) An image to display.
+     *            'text' - (array) The text to display.
+     * 'type' - (string) The MIME type of the rendered data.
+     * </pre>
+     */
+    public function render($mode)
+    {
+        switch ($mode) {
+        case 'full':
+            return $this->_render();
+
+        case 'inline':
+            return $this->_renderInline();
+
+        case 'info':
+            return $this->_renderInfo();
+        }
+    }
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $viewer = $this->_getViewer();
+        return $viewer
+            ? $viewer->render('full')
+            : array();
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        $viewer = $this->_getViewer();
+        return $viewer
+            ? $viewer->render('inline')
+            : array();
+    }
+
+    /**
+     * Return the rendered information about the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInfo()
+    {
+        $viewer = $this->_getViewer();
+        return $viewer
+            ? $viewer->render('info')
+            : array();
+    }
+
+    /**
+     * Can this driver render the the data?
+     *
+     * @param string $mode  The mode.  Either 'full', 'inline', or 'info'.
+     *
+     * @return boolean  True if the driver can render the data for the given
+     *                  view.
+     */
+    public function canRender($mode)
+    {
+        $viewer = $this->_getViewer();
+        if ($viewer) {
+            return $viewer->canRender($mode);
+        }
+
+        switch ($mode) {
+        case 'full':
+        case 'info':
+            return $this->_capability[$mode];
+
+        case 'inline':
+            return $this->getConfigParam('inline') &&
+                ($this->_capability['forceinline'] ||
+                 ($this->_capability['inline'] &&
+                  ($this->_mimepart->getDisposition() == 'inline')));
+
+        default:
+            return false;
+        }
+    }
+
+    /**
+     * Does this MIME part possibly contain embedded MIME parts?
+     *
+     * @return boolean  True if this driver supports parsing embedded MIME
+     *                  parts.
+     */
+    public function embeddedMimeParts()
+    {
+        $viewer = $this->_getViewer();
+        return ($viewer) ? $viewer->embeddedMimeParts() : $this->_capability['embedded'];
+    }
+
+    /**
+     * If this MIME part can contain embedded MIME parts, and those embedded
+     * MIME parts exist, return a list of MIME parts that contain the embedded
+     * MIME part information.
+     *
+     * @return mixed  An array of Horde_Mime_Part objects, with the key as
+     *                the ID, or null if no embedded MIME parts exist.
+     */
+    public function getEmbeddedMimeParts()
+    {
+        $viewer = $this->_getViewer();
+        return ($viewer)
+            ? $viewer->getEmbeddedMimeParts()
+            : $this->_getEmbeddedMimeParts();
+    }
+
+    /**
+     * If this MIME part can contain embedded MIME parts, and those embedded
+     * MIME parts exist, return a list of MIME parts that contain the embedded
+     * MIME part information.
+     *
+     * @return mixed  An array of Horde_Mime_Part objects, with the key as
+     *                the ID, or null if no embedded MIME parts exist.
+     */
+    protected function _getEmbeddedMimeParts()
+    {
+        return null;
+    }
+
+    /**
+     * Return a configuration parameter for the current viewer.
+     *
+     * @param string $param  The parameter name.
+     *
+     * @return mixed  The value of the parameter; returns null if the
+     *                parameter doesn't exist.
+     */
+    public function getConfigParam($param)
+    {
+        return isset($this->_conf[$param]) ? $this->_conf[$param] : null;
+    }
+
+    /**
+     * Returns the driver name for the current object.
+     *
+     * @return string  The driver name.
+     */
+    public function getDriver()
+    {
+        return $this->_conf['_driver'];
+    }
+
+    /**
+     * Return the underlying MIME Viewer for this part.
+     *
+     * @return mixed  A Horde_Mime_Viewer object, or false if not found.
+     */
+    protected function _getViewer()
+    {
+        return false;
+    }
+
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/audio.php b/framework/Mime/lib/Horde/Mime/Viewer/audio.php
new file mode 100644 (file)
index 0000000..f5966cb
--- /dev/null
@@ -0,0 +1,44 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_audio class sends audio parts to the browser for
+ * handling by the browser, a plugin, or a helper application.
+ *
+ * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_audio extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_mimepart->getContents(),
+                'status' => array(),
+                'type' => $this->_mimepart->getType()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/css.php b/framework/Mime/lib/Horde/Mime/Viewer/css.php
new file mode 100644 (file)
index 0000000..1f19d0d
--- /dev/null
@@ -0,0 +1,130 @@
+<?php
+
+require_once dirname(__FILE__) . '/source.php';
+
+/**
+ * The Horde_Mime_Viewer_css class renders CSS source as HTML with an effort
+ * to remove potentially malicious code.
+ *
+ * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_css extends Horde_Mime_Viewer_source
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Attribute preg patterns.
+     *
+     * @var array
+     */
+    protected $_attrPatterns = array(
+        // Attributes
+        '!([-\w]+\s*):!s' => '<span class="attr"">\\1</span>:',
+        // Values
+        '!:(\s*)(.+?)(\s*;)!s' => ':\\1<span class="value">\\2</span><span class="eol">\\3</span>',
+        // URLs
+        '!(url\([\'"]?)(.*?)([\'"]?\))!s' => '<span class="url">\\1<span class="file">\\2</span>\\3</span>',
+        // Colors
+        '!(#[[:xdigit:]]{3,6})!s' => '<span class="color">\\1</span>',
+        // Parentheses
+        '!({|})!s' => '<span class="parentheses">\\1</span>',
+        // Unity
+        '!(em|px|%)\b!s' => '<em>\\1</em>'
+    );
+
+    /**
+     * Handles preg patterns.
+     *
+     * @var array
+     */
+    protected $_handlesPatterns = array(
+        // HTML Tags
+        '!\b(body|h\d|a|span|div|acronym|small|strong|em|pre|ul|ol|li|p)\b!s' => '<span class="htag">\\1</span>\\2',
+        // IDs
+        '!(#[-\w]+)!s' => '<span class="id">\\1</span>',
+        // Class
+        '!(\.[-\w]+)\b!s' => '<span class="class">\\1</span>',
+        // METAs
+        '!(:link|:visited|:hover|:active|:first-letter)!s' => '<span class="metac">\\1</span>'
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $ret = $this->_renderInline();
+
+        // Need Horde headers for CSS tags.
+        reset($ret);
+        $ret[key($ret)]['data'] =  Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-header.inc') .
+            $ret[key($ret)]['data'] .
+            Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-footer.inc');
+
+        return $ret;
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        $css = preg_replace_callback('!(}|\*/).*?({|/\*)!s', array($this, '_handles'), htmlspecialchars($this->_mimepart->getContents(), ENT_NOQUOTES));
+        $css = preg_replace_callback('!{[^}]*}!s', array($this, '_attributes'), $css);
+        $css = preg_replace_callback('!/\*.*?\*/!s', array($this, '_comments'), $css);
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_lineNumber(trim($css)),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * TODO
+     */
+    protected function _comments($matches)
+    {
+        return '<span class="comment">' .
+            preg_replace('!(http://[/\w-.]+)!s', '<a href="\\1">\\1</a>', $matches[0]) .
+            '</span>';
+    }
+
+    /**
+     * TODO
+     */
+    protected function _attributes($matches)
+    {
+        return preg_replace(array_keys($this->_attrPatterns), array_values($this->_attrPatterns), $matches[0]);
+    }
+
+    /**
+     * TODO
+     */
+    protected function _handles($matches)
+    {
+        return preg_replace(array_keys($this->_handlesPatterns), array_values($this->_handlesPatterns), $matches[0]);
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/deb.php b/framework/Mime/lib/Horde/Mime/Viewer/deb.php
new file mode 100644 (file)
index 0000000..acf3606
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_deb class renders out lists of files in Debian
+ * packages by using the dpkg tool to query the package.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_deb extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => true,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $ret = $this->_renderInline();
+        if (!empty($ret)) {
+            reset($ret);
+            $ret[key($ret)]['data'] = '<html><body>' . $ret[key($ret)]['data'] . '</body></html>';
+        }
+        return $ret;
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        $tmp_deb = Horde::getTempFile('horde_deb');
+
+        file_put_contents($tmp_deb, $this->_mimepart->getContents());
+
+        $fh = popen($this->_conf['location'] . " -f $tmp_deb 2>&1", 'r');
+        while (($rc = fgets($fh, 8192))) {
+            $data .= $rc;
+        }
+        pclose($fh);
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => '<pre>' . htmlspecialchars($data) . '</pre>',
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/default.php b/framework/Mime/lib/Horde/Mime/Viewer/default.php
new file mode 100644 (file)
index 0000000..2d2b4bc
--- /dev/null
@@ -0,0 +1,17 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_default class simply prints out the encapsulated
+ * content.  It exists as a fallback if no other intelligent rendering
+ * mechanism could be used.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_default extends Horde_Mime_Viewer_Driver
+{
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/enriched.php b/framework/Mime/lib/Horde/Mime/Viewer/enriched.php
new file mode 100644 (file)
index 0000000..60777f5
--- /dev/null
@@ -0,0 +1,198 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_enriched class renders out plain text from enriched
+ * content tags, ala RFC 1896.
+ *
+ * By RFC, we must do the minimal conformance measures of: A minimal
+ * text/enriched implementation is one that converts "<<" to "<",
+ * removes everything between a <param> command and the next balancing
+ * </param> removes all other formatting commands (all text enclosed
+ * in angle brackets), and outside of <nofill> environments converts
+ * any series of n CRLFs to n-1 CRLFs, and converts any lone CRLF
+ * pairs to SPACE.
+ *
+ * We don't qualify as we don't currently track the <nofill>
+ * environment, that is we do CRLF conversion even if <nofill> is
+ * specified in the text, but we're close at least.
+ *
+ * Copyright 2001-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Eric Rostetter <eric.rostetter@physics.utexas.edu>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_enriched extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * This driver's capabilities.
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => '<html><body>' . $this->_toHTML() . '</body></html>',
+                'status' => array(),
+                'type' => 'text/html; charset=' . $this->_mimepart->getCharset()
+            )
+        );
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => String::convertCharset($this->_toHTML(), $this->_mimepart->getCharset()),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Convert the enriched text to HTML.
+     *
+     * @return string  The HTML-ified version of the MIME part contents.
+     */
+    protected function _toHTML()
+    {
+        $text = trim($this->_mimepart->getContents());
+        if (!strlen($text)) {
+            return array();
+        }
+
+        // We add space at the beginning and end of the string as it will
+        // make some regular expression checks later much easier (so we
+        // don't have to worry about start/end of line characters)
+        $text = ' ' . $text . ' ';
+
+        // We need to preserve << tags, so map them to ascii 1 or ascii 255
+        // We make the assumption here that there would never be an ascii
+        // 1 in an email, which may not be valid, but seems reasonable...
+        // ascii 255 would work if for some reason you don't like ascii 1
+        // ascii 0 does NOT seem to work for this, though I'm not sure why
+        $text = str_replace('<<', chr(1), $text);
+
+        // Remove any unrecognized tags in the text (via RFC minimal specs)
+        // any tags we just don't want to implement can also be removed here
+        // Note that this will remove any html links, but this is intended
+        $implementedTags = '<param><bold><italic><underline><fixed><excerpt>' .
+                           '<smaller><bigger><center><color><fontfamily>' .
+                           '<flushleft><flushright><flushboth><paraindent>';
+        // $unImplementedTags = '<nofill><lang>';
+        $text = strip_tags($text, $implementedTags);
+
+        // restore the << tags as < tags now...
+        $text = str_replace(chr(1), '<<', $text);
+        // $text = str_replace(chr(255), '<', $text);
+
+        $replace = array(
+            // Get color parameters into a more useable format.
+            '/<color><param>([\da-fA-F]+),([\da-fA-F]+),([\da-fA-F]+)<\/param>/Uis' => '<color r=\1 g=\2 b=\3>',
+            '/<color><param>(red|blue|green|yellow|cyan|magenta|black|white)<\/param>/Uis' => '<color n=\1>',
+
+            // Get font family parameters into a more useable format.
+            '/<fontfamily><param>(\w+)<\/param>/Uis' => '<fontfamily f=\1>',
+
+            /* Just remove any remaining parameters -- we won't use them.
+             * Any tags with parameters that we want to implement will have
+             * come before this. Someday we hope to use these tags (e.g. for
+             * <color><param> tags). */
+            '/<param>.*<\/param>/Uis' => '',
+
+            /* Single line breaks become spaces, double line breaks are a
+             * real break. This needs to do <nofill> tracking to be compliant
+             * but we don't want to deal with state at this time, so we fake
+             * it some day we should rewrite this to handle <nofill>
+             * correctly. */
+            '/([^\n])\r\n([^\r])/' => '\1 \2',
+            '/(\r\n)\r\n/' => '\1'
+        );
+        $text = preg_replace(array_keys($replace), array_values($replace), $text);
+
+        // We try to protect against bad stuff here.
+        $text = @htmlspecialchars($text, ENT_QUOTES, $this->_mimepart->getCharset());
+
+        // Now convert the known tags to html. Try to remove any tag
+        // parameters to stop people from trying to pull a fast one
+        $replace = array(
+            '/(?<!&lt;)&lt;bold.*&gt;(.*)&lt;\/bold&gt;/Uis' => '<span style="font-weight: bold">\1</span>',
+            '/(?<!&lt;)&lt;italic.*&gt;(.*)&lt;\/italic&gt;/Uis' => '<span style="font-style: italic">\1</span>',
+            '/(?<!&lt;)&lt;underline.*&gt;(.*)&lt;\/underline&gt;/Uis' => '<span style="text-decoration: underline">\1</span>'
+        );
+        $text = preg_replace(array_keys($replace), array_values($replace), $text);
+
+        $text = preg_replace_callback('/(?<!&lt;)&lt;color r=([\da-fA-F]+) g=([\da-fA-F]+) b=([\da-fA-F]+)&gt;(.*)&lt;\/color&gt;/Uis', array($this, 'colorize'), $text);
+
+        $replace = array(
+            '/(?<!&lt;)&lt;color n=(red|blue|green|yellow|cyan|magenta|black|white)&gt;(.*)&lt;\/color&gt;/Uis' => '<span style="color: \1">\2</span>',
+            '/(?<!&lt;)&lt;fontfamily&gt;(.*)&lt;\/fontfamily&gt;/Uis' => '\1',
+            '/(?<!&lt;)&lt;fontfamily f=(\w+)&gt;(.*)&lt;\/fontfamily&gt;/Uis' => '<span style="font-family: \1">\2</span>',
+            '/(?<!&lt;)&lt;smaller.*&gt;/Uis' => '<span style="font-size: smaller">',
+            '/(?<!&lt;)&lt;\/smaller&gt;/Uis' => '</span>',
+            '/(?<!&lt;)&lt;bigger.*&gt;/Uis' => '<span style="font-size: larger">',
+            '/(?<!&lt;)&lt;\/bigger&gt;/Uis' => '</span>',
+            '/(?<!&lt;)&lt;fixed.*&gt;(.*)&lt;\/fixed&gt;/Uis' => '<font face="fixed">\1</font>',
+            '/(?<!&lt;)&lt;center.*&gt;(.*)&lt;\/center&gt;/Uis' => '<div align="center">\1</div>',
+            '/(?<!&lt;)&lt;flushleft.*&gt;(.*)&lt;\/flushleft&gt;/Uis' => '<div align="left">\1</div>',
+            '/(?<!&lt;)&lt;flushright.*&gt;(.*)&lt;\/flushright&gt;/Uis' => '<div align="right">\1</div>',
+            '/(?<!&lt;)&lt;flushboth.*&gt;(.*)&lt;\/flushboth&gt;/Uis' => '<div align="justify">\1</div>',
+            '/(?<!&lt;)&lt;paraindent.*&gt;(.*)&lt;\/paraindent&gt;/Uis' => '<blockquote>\1</blockquote>',
+            '/(?<!&lt;)&lt;excerpt.*&gt;(.*)&lt;\/excerpt&gt;/Uis' => '<blockquote>\1</blockquote>'
+        );
+        $text = preg_replace(array_keys($replace), array_values($replace), $text);
+
+        // Replace << with < now (from translated HTML form).
+        $text = str_replace('&lt;&lt;', '&lt;', $text);
+
+        // Now we remove the leading/trailing space we added at the
+        // start.
+        $text = preg_replace('/^ (.*) $/s', '\1', $text);
+
+        // Make URLs clickable.
+        require_once 'Horde/Text/Filter.php';
+        $text = Text_Filter::filter($text, 'linkurls', array('callback' => 'Horde::externalUrl'));
+
+        /* Wordwrap -- note this could impact on our above RFC compliance *IF*
+         * we honored nofill tags (which we don't yet). */
+        $text = str_replace(array("\t", '  ', "\n "), array('        ', ' &nbsp;', "\n&nbsp;"), $text);
+
+        if ($text[0] == ' ') {
+            $text = '&nbsp;' . substr($text, 1);
+        }
+
+        return '<p class="fixed">' . nl2br($text) . '</p>';
+    }
+
+    /**
+     * TODO
+     */
+    public function colorize($colors)
+    {
+        for ($i = 1; $i < 4; $i++) {
+            $colors[$i] = sprintf('%02X', round(hexdec($colors[$i]) / 255));
+        }
+        return '<span style="color: #' . $colors[1] . $colors[2] . $colors[3] . '">' . $colors[4] . '</span>';
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/enscript.php b/framework/Mime/lib/Horde/Mime/Viewer/enscript.php
new file mode 100644 (file)
index 0000000..2e81e46
--- /dev/null
@@ -0,0 +1,168 @@
+<?php
+
+require_once dirname(__FILE__) . '/source.php';
+
+/**
+ * The Horde_Mime_Viewer_enscript class renders out various content in HTML
+ * format by using GNU Enscript.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_enscript extends Horde_Mime_Viewer_source
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_toHTML(false),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_toHTML(true),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Converts the code to HTML.
+     *
+     * @param boolean $inline  Is this an inline display?
+     *
+     * @return string  The HTML-ified version of the MIME part contents.
+     */
+    protected function _toHTML($inline)
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        /* Create temporary files for input to Enscript. Note that we can't
+         * use a pipe, since enscript must have access to the whole file to
+         * determine its type for coloured syntax highlighting. */
+        $tmpin = Horde::getTempFile('enscriptin');
+
+        /* Write the contents of our buffer to the temporary input file. */
+        file_put_contents($tmpin, $this->_mimepart->getContents());
+
+        /* Execute the enscript command. */
+        $lang = escapeshellarg($this->_typeToLang($this->_mimepart->getType()));
+        $results = shell_exec($this->_conf['location'] . " -E$lang --language=html --color --output=- < $tmpin");
+
+        /* Strip out the extraneous HTML from enscript. */
+        if ($inline) {
+            $res_arr = preg_split('/\<\/?pre\>/i', $results);
+            if (count($res_arr) == 3) {
+                $results = trim($res_arr[1]);
+            }
+        }
+
+        return $this->_lineNumber($results);
+    }
+
+    /**
+     * Attempts to determine what language to use for the enscript program
+     * from a MIME type.
+     *
+     * @param string $type  The MIME type.
+     *
+     * @return string  The enscript 'language' parameter string.
+     */
+    protected function _typeToLang($type)
+    {
+        include_once dirname(__FILE__) . '/../Magic.php';
+        $ext = Horde_Mime_Magic::MIMEToExt($type);
+
+        switch ($ext) {
+        case 'cs':
+            return 'java';
+
+        case 'el':
+            return 'elisp';
+
+        case 'h':
+            return 'c';
+
+        case 'C':
+        case 'H':
+        case 'cc':
+        case 'hh':
+        case 'c++':
+        case 'cxx':
+        case 'cpp':
+            return 'cpp';
+
+        case 'htm':
+        case 'shtml':
+        case 'xml':
+            return 'html';
+
+        case 'js':
+            return 'javascript';
+
+        case 'pas':
+            return 'pascal';
+
+        case 'al':
+        case 'cgi':
+        case 'pl':
+        case 'pm':
+            return 'perl';
+
+        case 'ps':
+            return 'postscript';
+
+        case 'vb':
+            return 'vba';
+
+        case 'vhd':
+            return 'vhdl';
+
+        case 'patch':
+        case 'diff':
+            return 'diffu';
+
+        default:
+            return $ext;
+        }
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/html.php b/framework/Mime/lib/Horde/Mime/Viewer/html.php
new file mode 100644 (file)
index 0000000..3d5e95a
--- /dev/null
@@ -0,0 +1,197 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_html class renders out HTML text with an effort to
+ * remove potentially malicious code.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @author  Jon Parise <jon@horde.org>
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_html extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $html = $this->_cleanHTML($this->_mimepart->getContents(), false);
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $html['data'],
+                'status' => array(),
+                'type' => $this->_mimepart->getType(true)
+            )
+        );
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        $html = $this->_cleanHTML($this->_mimepart->getContents(), true);
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => String::convertCharset($html['data'], $this->_mimepart->getCharset()),
+                'status' => $html['status'],
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Filters active content, dereferences external links, detects phishing,
+     * etc.
+     *
+     * @todo Use IP checks from
+     * http://lxr.mozilla.org/mailnews/source/mail/base/content/phishingDetector.js.
+     *
+     * @param string $data     The HTML data.
+     * @param boolean $inline  Are we viewing inline?
+     *
+     * @return array  Two elements: 'html' and 'status'.
+     */
+    protected function _cleanHTML($data, $inline)
+    {
+        global $browser, $prefs;
+
+        $phish_warn = false;
+
+        /* Deal with <base> tags in the HTML, since they will screw up our own
+         * relative paths. */
+        if (preg_match('/<base href="?([^"> ]*)"? ?\/?>/i', $data, $matches)) {
+            $base = $matches[1];
+            if (substr($base, -1) != '/') {
+                $base .= '/';
+            }
+
+            /* Recursively call _cleanHTML() to prevent clever fiends from
+             * sneaking nasty things into the page via $base. */
+            $base = $this->_cleanHTML($base, $inline);
+
+            /* Attempt to fix paths that were relying on a <base> tag. */
+            if (!empty($base)) {
+                $pattern = array('|src=(["\'])([^:"\']+)\1|i',
+                                 '|src=([^: >"\']+)|i',
+                                 '|href= *(["\'])([^:"\']+)\1|i',
+                                 '|href=([^: >"\']+)|i');
+                $replace = array('src=\1' . $base . '\2\1',
+                                 'src=' . $base . '\1',
+                                 'href=\1' . $base . '\2\1',
+                                 'href=' . $base . '\1');
+                $data = preg_replace($pattern, $replace, $data);
+            }
+        }
+
+        require_once 'Horde/Text/Filter.php';
+        $strip_style_attributes = (($browser->isBrowser('mozilla') &&
+                                    $browser->getMajor() == 4) ||
+                                   $browser->isBrowser('msie'));
+        $strip_styles = $inline || $strip_style_attributes;
+        $data = Text_Filter::filter($data, 'xss',
+                                    array('body_only' => $inline,
+                                          'strip_styles' => $strip_styles,
+                                          'strip_style_attributes' => $strip_style_attributes));
+
+        /* Check for phishing exploits. */
+        if ($this->getConfigParam('phishing_check')) {
+            if (preg_match('/href\s*=\s*["\']?\s*(http|https|ftp):\/\/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})(?:[^>]*>\s*(?:\\1:\/\/)?(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})[^<]*<\/a)?/i', $data, $m)) {
+                /* Check 1: Check for IP address links, but ignore if the link
+                 * text has the same IP address. */
+                if (!isset($m[3]) || ($m[2] != $m[3])) {
+                    if (isset($m[3])) {
+                        $data = preg_replace('/href\s*=\s*["\']?\s*(http|https|ftp):\/\/' . preg_quote($m[2], '/') . '(?:[^>]*>\s*(?:$1:\/\/)?' . preg_quote($m[3], '/') . '[^<]*<\/a)?/i', 'class="mimeStatusWarning" $0', $data);
+                    }
+                    $phish_warn = true;
+                }
+            } elseif (preg_match_all('/href\s*=\s*["\']?\s*(?:http|https|ftp):\/\/([^\s"\'>]+)["\']?[^>]*>\s*(?:(?:http|https|ftp):\/\/)?(.*?)<\/a/is', $data, $m)) {
+                /* $m[1] = Link; $m[2] = Target
+                 * Check 2: Check for links that point to a different host than
+                 * the target url; if target looks like a domain name, check it
+                 * against the link. */
+                for ($i = 0, $links = count($m[0]); $i < $links; ++$i) {
+                    $link = strtolower(urldecode($m[1][$i]));
+                    $target = strtolower(preg_replace('/^(http|https|ftp):\/\//', '', strip_tags($m[2][$i])));
+                    if (preg_match('/^[-._\da-z]+\.[a-z]{2,}/i', $target) &&
+                        (strpos($link, $target) !== 0) &&
+                        (strpos($target, $link) !== 0)) {
+                        /* Don't consider the link a phishing link if the
+                         * domain is the same on both links (e.g.
+                         * adtracking.example.com & www.example.com). */
+                        preg_match('/\.?([^\.\/]+\.[^\.\/]+)[\/?]/', $link, $host1);
+                        preg_match('/\.?([^\.\/]+\.[^\.\/ ]+)([\/ ].*)?$/', $target, $host2);
+                        if (!(count($host1) && count($host2)) ||
+                            (strcasecmp($host1[1], $host2[1]) !== 0)) {
+                            $data = preg_replace('/href\s*=\s*["\']?\s*(?:http|https|ftp):\/\/' . preg_quote($m[1][$i], '/') . '["\']?[^>]*>\s*(?:(?:http|https|ftp):\/\/)?' . preg_quote($m[2][$i], '/') . '<\/a/is', 'class="mimeStatusWarning" $0', $data);
+                            $phish_warn = true;
+                        }
+                    }
+                }
+            }
+        }
+
+        /* Try to derefer all external references. */
+        $data = preg_replace_callback('/href\s*=\s*(["\'])?((?(1)[^\1]*?|[^\s>]+))(?(1)\1|)/i', array($this, '_dereferCallback'), $data);
+
+        /* Get phishing warning. */
+        $status = array();
+        if ($inline && $phish_warn) {
+            $warning = array(
+                sprintf(_("%s: This message may not be from whom it claims to be. Beware of following any links in it or of providing the sender with any personal information."), _("Warning")),
+                _("The links that caused this warning have this background color:") . ' <span class="mimeStatusWarning">' . _("EXAMPLE") . '.</span>'
+            );
+
+            if (!$inline) {
+                $temp = array();
+                foreach ($phish_warning as $val) {
+                    $temp[] = String::convertCharset($val, NLS::getCharset(), $this->_mimepart->getCharset());
+                }
+                $warning = $temp;
+            }
+
+            $status[] = array(
+                'class' => 'mimestatuswarning',
+                'text' => $warning
+            );
+        }
+
+        return array('html' => $data, 'status' => $status);
+    }
+
+    /**
+     * TODO
+     *
+     * @param string $m  TODO
+     *
+     * @return string  TODO
+     */
+    protected function _dereferCallback($m)
+    {
+        return 'href="' . Horde::externalUrl($m[2]) . '"';
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/images.php b/framework/Mime/lib/Horde/Mime/Viewer/images.php
new file mode 100644 (file)
index 0000000..6379bd5
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_images class allows images to be displayed.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_images extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_mimepart->getContents(),
+                'status' => array(),
+                'type' => $this->_getType()
+            )
+        );
+    }
+
+    /**
+     * Return the content-type to use for the image.
+     *
+     * @return string  The content-type of the image.
+     */
+    protected function _getType()
+    {
+        $type = $this->_mimepart->getType();
+
+        switch ($type) {
+        case 'image/pjpeg':
+            /* image/jpeg and image/pjpeg *appear* to be the same entity, but
+             * Mozilla (for one) don't seem to want to accept the latter. */
+            return 'image/jpeg';
+
+        case 'image/x-png':
+            /* image/x-png == image/png. */
+            return 'image/png';
+
+        default:
+            return $type;
+        }
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/msexcel.php b/framework/Mime/lib/Horde/Mime/Viewer/msexcel.php
new file mode 100644 (file)
index 0000000..e934c73
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_msexcel class renders out Microsoft Excel
+ * documents in HTML format by using the xlHtml package.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_msexcel extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        $data = '';
+        $tmp_xls = Horde::getTempFile('horde_msexcel');
+
+        file_put_contents($tmp_xls, $this->_mimepart->getContents());
+
+        $fh = popen($this->_conf['location'] . " -nh $tmp_xls 2>&1", 'r');
+        while (($rc = fgets($fh, 8192))) {
+            $data .= $rc;
+        }
+        pclose($fh);
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $data,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/mspowerpoint.php b/framework/Mime/lib/Horde/Mime/Viewer/mspowerpoint.php
new file mode 100644 (file)
index 0000000..851b440
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_mspowerpoint class renders out Microsoft Powerpoint
+ * documents in HTML format by using the xlHtml package.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_mspowerpoint extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        $data = '';
+        $tmp_ppt = Horde::getTempFile('horde_mspowerpoint');
+
+        file_put_contents($tmp_ppt, $this->_mimepart->getContents());
+
+        $fh = popen($this->_conf['location'] . " $tmp_ppt 2>&1", 'r');
+        while (($rc = fgets($fh, 8192))) {
+            $data .= $rc;
+        }
+        pclose($fh);
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $data,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/msword.php b/framework/Mime/lib/Horde/Mime/Viewer/msword.php
new file mode 100644 (file)
index 0000000..68874e4
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_msword class renders out Microsoft Word documents
+ * in HTML format by using the wvWare package.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_msword extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        $charset = NLS::getCharset();
+        $tmp_word = Horde::getTempFile('msword');
+        $tmp_output = Horde::getTempFile('msword');
+        $tmp_dir = Horde::getTempDir();
+        $tmp_file = str_replace($tmp_dir . '/', '', $tmp_output);
+
+        if (OS_WINDOWS) {
+            $args = ' -x ' . dirname($this->_conf['location']) . "\\wvHtml.xml -d $tmp_dir -1 $tmp_word > $tmp_output";
+        } else {
+            $version = exec($this->_conf['location'] . ' --version');
+            $args = (version_compare($version, '0.7.0') >= 0)
+                ? " --charset=$charset --targetdir=$tmp_dir $tmp_word $tmp_file"
+                : " $tmp_word $tmp_output";
+        }
+
+        $fh = fopen($tmp_word, 'w');
+        fwrite($fh, $this->_mimepart->getContents());
+        fclose($fh);
+
+        exec($this->_conf['location'] . $args);
+
+        if (file_exists($tmp_output)) {
+            return array(
+                $this->_mimepart->getMimeId() => array(
+                    'data' => file_get_contents($tmp_output),
+                    'status' => array(),
+                    'type' => 'text/html; charset=' . $charset
+                )
+            );
+        }
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => _("Unable to translate this Word document"),
+                'status' => array(),
+                'type' => 'text/plain; charset=' . $charset
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo.php b/framework/Mime/lib/Horde/Mime/Viewer/ooo.php
new file mode 100644 (file)
index 0000000..7cc8d17
--- /dev/null
@@ -0,0 +1,116 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_ooo class renders out OpenOffice.org documents in
+ * HTML format.
+ *
+ * Copyright 2003-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Marko Djukic <marko@oblo.com>
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_ooo extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $has_xslt = Util::extensionExists('xslt');
+        $has_ssfile = function_exists('domxml_xslt_stylesheet_file');
+        if (($use_xslt = $has_xslt || $has_ssfile)) {
+            $tmpdir = Util::createTempDir(true);
+        }
+
+        $fnames = array('content.xml', 'styles.xml', 'meta.xml');
+        $tags = array(
+            'text:p' => 'p',
+            'table:table' => 'table border="0" cellspacing="1" cellpadding="0" ',
+            'table:table-row' => 'tr bgcolor="#cccccc"',
+            'table:table-cell' => 'td',
+            'table:number-columns-spanned=' => 'colspan='
+        );
+
+        require_once 'Horde/Compress.php';
+        $zip = &Horde_Compress::singleton('zip');
+        $list = $zip->decompress($this->_mimepart->getContents(), array('action' => HORDE_COMPRESS_ZIP_LIST));
+
+        foreach ($list as $key => $file) {
+            if (in_array($file['name'], $fnames)) {
+                $content = $zip->decompress($this->_mimepart->getContents(), array(
+                    'action' => HORDE_COMPRESS_ZIP_DATA,
+                    'info' => $list,
+                    'key' => $key
+                ));
+
+                if ($use_xslt) {
+                    file_put_contents($tmpdir . $file['name'], $content);
+                } elseif ($file['name'] == 'content.xml') {
+                    return array(
+                        $this->_mimepart->getMimeId() => array(
+                            'data' => str_replace(array_keys($tags), array_values($tags), $content),
+                            'status' => array(),
+                            'type' => 'text/html; charset=UTF-8'
+                        )
+                    );
+                }
+            }
+        }
+
+        if (!Util::extensionExists('xslt')) {
+            return array();
+        }
+
+        $xsl_file = dirname(__FILE__) . '/ooo/main_html.xsl';
+
+        if ($has_ssfile) {
+            /* Use DOMXML */
+            $xslt = domxml_xslt_stylesheet_file($xsl_file);
+            $dom  = domxml_open_file($tmpdir . 'content.xml');
+            $result = @$xslt->process($dom, array(
+                'metaFileURL' => $tmpdir . 'meta.xml',
+                'stylesFileURL' => $tmpdir . 'styles.xml',
+                'disableJava' => true)
+            );
+            $result = $xslt->result_dump_mem($result);
+        } else {
+            // Use XSLT
+            $xslt = xslt_create();
+            $result = @xslt_process($xslt, $tmpdir . 'content.xml', $xsl_file, null, null, array(
+                'metaFileURL' => $tmpdir . 'meta.xml',
+                'stylesFileURL' => $tmpdir . 'styles.xml',
+                'disableJava' => true)
+            );
+            if (!$result) {
+                $result = xslt_error($xslt);
+            }
+            xslt_free($xslt);
+        }
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $result,
+                'status' => array(),
+                'type' => 'text/html; charset=UTF-8'
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/common.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/common.xsl
new file mode 100644 (file)
index 0000000..943a5b9
--- /dev/null
@@ -0,0 +1,1165 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                extension-element-prefixes="xt"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+
+
+    <!-- ************ -->
+    <!-- *** body *** -->
+    <!-- ************ -->
+
+
+    <xsl:template match="/*/office:body">
+        <xsl:param name="collectedGlobalData"/>
+
+        <!-- isDebugMode-START: only isDebugMode purpose: shows the inlined style attributes of the temporary variable -->
+        <xsl:if test="$isDebugMode and not($outputType = 'CSS_HEADER')">
+            <xsl:element name="debug_tree_of_styles"><xsl:text>
+            </xsl:text><xsl:for-each select="$collectedGlobalData/allstyles/*">
+<xsl:text>                      </xsl:text><xsl:value-of select="name()"/><xsl:text> = </xsl:text><xsl:value-of select="."/><xsl:text>
+            </xsl:text>
+                    </xsl:for-each>
+            </xsl:element>
+        </xsl:if>
+    <!-- isDebugMode-END -->
+
+
+               <!-- not using of 'apply-styles-and-content' as the content table information migth have been added to 'collectedGlobalData' variable -->
+        <xsl:apply-templates select="@text:style-name | @draw:style-name | @draw:text-style-name | @table:style-name"><!-- | @presentation:style-name -->
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:apply-templates>
+
+
+        <!-- Usability feature, a link to the Content talbe above all level 1 header -->
+        <xsl:if test="$contentTableHeadings">
+            <xsl:call-template name="add-child-document-usability-links"/>
+        </xsl:if>
+
+
+               <xsl:choose>
+               <xsl:when test="not($outputType = 'WML') and not($outputType = 'PALM')">
+                       <xsl:choose>
+                               <!--If the input document is a global document and embed child documents (links) the transformation of the children will be started as well.
+                                   This is necessary as child documents do not know anything about their embedding into a global document. Chapters of childs
+                                   always start to count by zero instead of continously numbering.
+                                   For this, the chapter numbers of the current document (as a sequence of a global document) is dependent
+                                   of the number of chapter of the same level in preceding documents.
+                                   In case of multiple children, for usability reasons some linking is gonna be offered and the URLs of the content-table,
+                                   preceding and following file have to be given for the transformation.
+                                   -->
+                           <xsl:when test="/*/@office:class='text-global' and /*/office:body/text:section/text:section-source/@xlink:href">
+                                               <!-- the children will be called later with a modified 'collectedGlobalData' variable -->
+                               <xsl:call-template name="transform-global-document-and-children">
+                                   <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                               </xsl:call-template>
+                           </xsl:when>
+                                       <xsl:otherwise>
+                                       <xsl:apply-templates>
+                                           <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                       </xsl:apply-templates>
+                                       </xsl:otherwise>
+                               </xsl:choose>
+               </xsl:when>
+                       <xsl:otherwise>
+                       <xsl:apply-templates>
+                           <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                       </xsl:apply-templates>
+                       </xsl:otherwise>
+               </xsl:choose>
+
+
+
+        <!-- Usability feature, a link to the Content talbe above all level 1 header -->
+        <xsl:if test="$contentTableHeadings">
+            <xsl:call-template name="add-child-document-usability-links"/>
+        </xsl:if>
+
+
+    </xsl:template>
+
+
+
+
+    <!-- deactivating default template -->
+    <xsl:template match="*"/>
+
+
+    <!-- allowing all matched text nodes -->
+    <xsl:template match="text()">
+<!-- WML         <xsl:value-of select="normalize-space(.)"/> -->
+        <xsl:value-of select="."/>
+    </xsl:template>
+
+
+
+    <!-- ################### -->
+    <!-- ### INLINE-TEXT ### -->
+    <!-- ################### -->
+
+
+    <!-- ****************** -->
+    <!-- *** Whitespace *** -->
+    <!-- ****************** -->
+
+
+    <xsl:template match="text:s">
+        <xsl:call-template name="write-breakable-whitespace">
+            <xsl:with-param name="whitespaces" select="@text:c"/>
+        </xsl:call-template>
+    </xsl:template>
+
+
+    <!--write the number of 'whitespaces' -->
+    <xsl:template name="write-breakable-whitespace">
+        <xsl:param name="whitespaces"/>
+
+        <!--write two space chars as the normal white space character will be stripped
+            and the other is able to break -->
+        <xsl:text>&#160;</xsl:text>
+        <xsl:if test="$whitespaces >= 2">
+            <xsl:call-template name="write-breakable-whitespace-2">
+                <xsl:with-param name="whitespaces" select="$whitespaces - 1"/>
+            </xsl:call-template>
+        </xsl:if>
+    </xsl:template>
+
+
+    <!--write the number of 'whitespaces' -->
+    <xsl:template name="write-breakable-whitespace-2">
+        <xsl:param name="whitespaces"/>
+        <!--write two space chars as the normal white space character will be stripped
+            and the other is able to break -->
+        <xsl:text> </xsl:text>
+        <xsl:if test="$whitespaces >= 2">
+            <xsl:call-template name="write-breakable-whitespace">
+                <xsl:with-param name="whitespaces" select="$whitespaces - 1"/>
+            </xsl:call-template>
+        </xsl:if>
+    </xsl:template>
+
+
+
+
+    <!-- *************** -->
+    <!-- *** Textbox *** -->
+    <!-- *************** -->
+
+    <xsl:template match="draw:text-box">
+        <xsl:param name="collectedGlobalData"/>
+
+            <xsl:choose>
+                <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+                <!--                or                  -->
+                <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+                <xsl:when test="$outputType = 'CSS_HEADER' or $outputType = 'CSS_INLINED'">
+                    <xsl:element name="span">
+                        <xsl:if test="@fo:min-height | @svg:width">
+                            <xsl:attribute name="style">
+                                <xsl:choose>
+                                    <xsl:when test="not(@svg:width)">
+                                        <xsl:text>height: </xsl:text><xsl:value-of select="@fo:min-height"/><xsl:text>; </xsl:text>
+                                    </xsl:when>
+                                    <xsl:when test="not(@fo:min-height)">
+                                        <xsl:text>width: </xsl:text><xsl:value-of select="@svg:width"/><xsl:text>; </xsl:text>
+                                    </xsl:when>
+                                    <xsl:otherwise>
+                                        <xsl:text>height: </xsl:text><xsl:value-of select="@fo:min-height"/><xsl:text>; </xsl:text>
+                                        <xsl:text>width: </xsl:text><xsl:value-of select="@svg:width"/><xsl:text>; </xsl:text>
+                                    </xsl:otherwise>
+                                </xsl:choose>
+                            </xsl:attribute>
+                        </xsl:if>
+                        <xsl:apply-templates select="@draw:name"/>
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:element>
+                </xsl:when>
+                <!-- 2DO prove best usage for PALM -->
+                <!--+++++ PALM 3.2 SUBSET INLINED WAY  +++++-->
+                <xsl:when test="$outputType = 'PALM'">
+                    <xsl:element name="span">
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:element>
+                </xsl:when>
+                <!-- 2DO prove best usage for WML -->
+                <!--+++++ WML / WAP  +++++-->
+                <xsl:otherwise>
+                    <!-- no nested p tags in wml1.1 allowed -->
+                    <xsl:choose>
+                        <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                            <xsl:call-template name="apply-styles-and-content">
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:call-template>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <xsl:element name="p">
+                                <xsl:call-template name="apply-styles-and-content">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                             </xsl:element>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </xsl:otherwise>
+            </xsl:choose>
+    </xsl:template>
+
+    <!-- ID / NAME of text-box -->
+    <xsl:template match="@draw:name">
+
+        <xsl:attribute name="id">
+            <xsl:value-of select="."/>
+        </xsl:attribute>
+    </xsl:template>
+
+
+
+    <!-- ****************** -->
+    <!-- *** Paragraphs *** -->
+    <!-- ****************** -->
+
+    <xsl:template match="text:p | draw:page">
+        <xsl:param name="collectedGlobalData"/>
+
+            <xsl:choose>
+                <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+                <xsl:when test="$outputType = 'CSS_HEADER'">
+                    <xsl:element name="p">
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:element>
+                </xsl:when>
+
+                <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+                <xsl:when test="$outputType = 'CSS_INLINED'">
+                    <xsl:element name="p">
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:element>
+                </xsl:when>
+                <!--+++++ PALM 3.2 SUBSET INLINED WAY  +++++-->
+                <xsl:when test="$outputType = 'PALM'">
+                    <xsl:choose>
+                        <!-- in palm paragraphs children of text:list-items are better shown without 'p' tag-->
+                        <xsl:when test="name(parent::*) = 'text:list-item'">
+                            <xsl:call-template name="apply-styles-and-content">
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:call-template>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <xsl:element name="p">
+                                <xsl:call-template name="apply-styles-and-content">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                            </xsl:element>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </xsl:when>
+                <!--+++++ WML / WAP  +++++-->
+                <xsl:otherwise>
+                    <!-- no nested p tags in wml1.1 allowed -->
+                    <xsl:choose>
+                        <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                            <xsl:call-template name="apply-styles-and-content">
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:call-template>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <xsl:element name="p">
+                                <xsl:call-template name="apply-styles-and-content">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                             </xsl:element>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </xsl:otherwise>
+            </xsl:choose>
+    </xsl:template>
+
+
+
+    <!-- ***************** -->
+    <!-- *** Text Span *** -->
+    <!-- ***************** -->
+
+    <xsl:template match="text:span">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:choose>
+            <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+            <xsl:when test="$outputType = 'CSS_HEADER'">
+                <xsl:element name="span">
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'CSS_INLINED'">
+                <xsl:element name="span">
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+            <!--+++++ PALM 3.2 SUBSET INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'PALM'">
+                <xsl:call-template name="apply-styles-and-content">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:when>
+            <!--+++++ WML / WAP  +++++-->
+            <xsl:otherwise>
+                <!-- no nested p tags in wml1.1 allowed -->
+                <xsl:choose>
+                    <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:element name="p">
+                            <xsl:call-template name="apply-styles-and-content">
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:call-template>
+                        </xsl:element>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <!-- **************** -->
+    <!-- *** Headings *** -->
+    <!-- **************** -->
+
+    <xsl:template match="text:h">
+        <xsl:param name="collectedGlobalData"/>
+
+        <!-- Every heading element will get an unique anchor for its file, from its hiearchy level and name:
+            For example:  The heading title 'My favorite heading' might get <a name="1+2+2+My+favorite+heading"/> -->
+        <xsl:choose>
+            <xsl:when test="$disableLinkedTableOfContent or $isJavaDisabled or not($outputType = 'CSS_HEADER')">
+            <!-- The URL linking of an table-of-content is due to a bug (cmp. bug id# 102311) not mapped as URL in the XML.
+                 Linking of the table-of-content can therefore only be archieved by a work-around in HTML -->
+                <xsl:call-template name="create-heading">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+           </xsl:when>
+           <xsl:otherwise>
+                <!-- necessary as anchor for the content table -->
+                <xsl:call-template name="create-heading-anchor">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+
+                <!-- no embedding the orginal header, as an explicit anchor might be already set -->
+                <xsl:call-template name="create-heading">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- default matching for header elements -->
+    <xsl:template name="create-heading">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:choose>
+            <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+            <xsl:when test="$outputType = 'CSS_HEADER'">
+
+                <xsl:variable name="headertyp" select="concat('h', @text:level)"/>
+                <xsl:element name="{$headertyp}">
+
+                    <!-- outline style 'text:min-label-width' is interpreted as a CSS 'margin-left' attribute -->
+                    <xsl:variable name="min-label" select="$office:styles/text:outline-style/text:outline-level-style[@text:level = current()/@text:level]/style:properties/@text:min-label-width"/>
+                    <xsl:if test="$min-label">
+                        <xsl:attribute name="style"><xsl:text>margin-left:</xsl:text><xsl:value-of select="$min-label"/><xsl:text>;</xsl:text></xsl:attribute>
+                    </xsl:if>
+
+
+                    <xsl:attribute name="class"><xsl:value-of select="translate(@text:style-name, '. %()/\', '')"/></xsl:attribute>
+
+                    <!-- writing out a chapter number if desired (noticable when a corresponding 'text:outline-style' exist -->
+                    <xsl:if test="string-length($office:styles/text:outline-style/text:outline-level-style[@text:level = current()/@text:level]/@style:num-format) != 0">
+
+                        <xsl:choose>
+                            <xsl:when test="$disableLinkedTableOfContent or $isJavaDisabled or not($outputType = 'CSS_HEADER')">
+                                <!-- the chapter number is the sum of 'text:start-value' and preceding siblings of 'text:h' with the same 'text:level',
+                                     furthermore when the current document is referenced by a global document - as part of a whole sequence of documents -,
+                                     the chapter no. is dependent of the amount of started headers in preceding documents.
+                                     If the 'text:start-value is not set the default value of '1' has to be taken. -->
+                                <xsl:variable name="startValue" select="$office:styles/text:outline-style/text:outline-level-style[@text:level = current()/@text:level]/@text:start-value"/>
+                                <xsl:choose>
+                                    <xsl:when test="$startValue">
+                                        <xsl:choose>
+                                            <xsl:when test="@text:level='1'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel1
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='2'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel2
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='3'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel3
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='4'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel4
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='5'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel5
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='6'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel6
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='7'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel7
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='8'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel8
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='9'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel9
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='10'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel10
+                                                            + $startValue"/>
+                                            </xsl:when>
+                                        </xsl:choose>
+                                    </xsl:when>
+                                    <xsl:otherwise>
+                                        <xsl:choose>
+                                            <xsl:when test="@text:level='1'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel1
+                                                            + 1"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='2'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel2
+                                                            + 1"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='3'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel3
+                                                            + 1"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='4'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel4
+                                                            + 1"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='5'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel5
+                                                            + 1"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='6'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel6
+                                                            + 1"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='7'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel7
+                                                            + 1"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='8'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel8
+                                                            + 1"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='9'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel9
+                                                            + 1"/>
+                                            </xsl:when>
+                                            <xsl:when test="@text:level='10'">
+                                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                            + $precedingChapterLevel10
+                                                            + 1"/>
+                                            </xsl:when>
+                                        </xsl:choose>
+                                    </xsl:otherwise>
+                                </xsl:choose>
+                            </xsl:when>
+                            <xsl:otherwise>
+                                <xsl:call-template name="get-absolute-chapter-no">
+                                    <xsl:with-param name="precedingChapterLevel1"    select="$precedingChapterLevel1"/>
+                                    <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                            </xsl:otherwise>
+                        </xsl:choose>
+                        <xsl:text> &#160; &#160;</xsl:text>
+                    </xsl:if>
+                    <xsl:apply-templates>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:apply-templates>
+
+                </xsl:element>
+            </xsl:when>
+
+
+            <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'CSS_INLINED'">
+                <xsl:variable name="headertyp" select="concat('h', @text:level)"/>
+                <xsl:element name="{$headertyp}">
+
+                    <xsl:apply-templates select="@text:style-name">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:apply-templates>
+
+                    <!-- writing out a chapter number if desired (noticable when a corresponding 'text:outline-style' exist -->
+                    <xsl:if test="$office:styles/text:outline-style/text:outline-level-style[@text:level = current()/@text:level]/@text:style-name">
+
+                        <!-- the chapter number is the sum of 'text:start-value' and preceding siblings of 'text:h' with the same 'text:level' -->
+                        <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                            + $office:styles/text:outline-style/text:outline-level-style[@text:level = current()/@text:level]/@text:start-value"/>
+                        <xsl:text> &#160; &#160;</xsl:text>
+                    </xsl:if>
+
+                </xsl:element>
+            </xsl:when>
+
+            <!-- 2DO: add Chapter No. for PALM and WML <-> problem nested apply-templates -->
+
+            <!--+++++ PALM 3.2 SUBSET INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'PALM'">
+                <xsl:variable name="headertyp" select="concat('h', @text:level)"/>
+                <xsl:element name="{$headertyp}">
+
+
+                    <!-- All children content have to be nested in the styles (e.g. <i><b>ANY CONTENT</b></i>)
+                         for this xsl:apply-templates will be called later / implicit -->
+                    <xsl:call-template name="create-attribute-ALIGN">
+                        <!-- getting the css styles for the style name (mapped by style-mapping.xsl) -->
+                        <xsl:with-param name="styleProperties" select="$collectedGlobalData/allstyles/*[name()=current()/@text:style-name]"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ WML / WAP  +++++-->
+            <xsl:otherwise>
+                <!-- no nested p tags in wml1.1 allowed -->
+                <xsl:choose>
+                    <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                        <!-- since no header styles exist, an emphasis is used -->
+                        <xsl:element name="em">
+
+                            <!-- writing out a chapter number if desired (noticable when a corresponding 'text:outline-style' exist -->
+                            <xsl:if test="$office:styles/text:outline-style/text:outline-level-style[@text:level = current()/@text:level]/@text:style-name">
+
+                                <!-- the chapter number is the sum of 'text:start-value' and preceding siblings of 'text:h' with the same 'text:level' -->
+                                <xsl:value-of select="count(preceding-sibling::text:h[@text:level = current()/@text:level])
+                                                    + $office:styles/text:outline-style/text:outline-level-style[@text:level = current()/@text:level]/@text:start-value"/>
+                                <xsl:text> &#160; &#160;</xsl:text>
+                            </xsl:if>
+
+                            <xsl:apply-templates select="@text:style-name">
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:apply-templates>
+
+                        </xsl:element>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:element name="p">
+                            <!-- since no header styles exist, an emphasis is used -->
+                            <xsl:element name="em">
+                                <xsl:call-template name="apply-styles-and-content">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                            </xsl:element>
+                        </xsl:element>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <!-- ************* -->
+    <!-- *** Link  *** -->
+    <!-- ************* -->
+
+    <xsl:template match="text:a | draw:a">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:call-template name="create-common-link">
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:call-template>
+    </xsl:template>
+
+
+    <xsl:template name="create-common-link">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:choose>
+            <xsl:when test="not($outputType = 'WML')">
+                <xsl:element name="a">
+                    <xsl:attribute name="href"><xsl:value-of select="@xlink:href"/></xsl:attribute>
+                    <!--<xsl:attribute name="class">ContentLink</xsl:attribute>-->
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+            <xsl:otherwise>
+                <!-- no nested p tags in wml1.1 allowed -->
+                <xsl:choose>
+                    <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                        <xsl:element name="a">
+                            <xsl:attribute name="href"><xsl:value-of select="@xlink:href"/></xsl:attribute>
+                            <xsl:apply-templates select="descendant::text()"/>
+                        </xsl:element>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:element name="p">
+                            <xsl:element name="a">
+                                <xsl:attribute name="href"><xsl:value-of select="@xlink:href"/></xsl:attribute>
+                                <xsl:apply-templates select="descendant::text()"/>
+                            </xsl:element>
+                        </xsl:element>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+
+
+    <!-- ******************* -->
+    <!-- *** Image Link  *** -->
+    <!-- ******************* -->
+
+    <xsl:template match="draw:image">
+        <xsl:param name="collectedGlobalData"/>
+
+        <!-- NO IMAGES SUPPLIED FOR WAP OR PALM -->
+        <xsl:if test="$outputType = 'CSS_HEADER' or $outputType = 'CSS_INLINED'">
+
+            <xsl:element name="img">
+                <xsl:if test="@svg:width">
+                    <xsl:attribute name="width">
+                        <xsl:call-template name="convert2pixel">
+                            <xsl:with-param name="value" select="@svg:width"/>
+                        </xsl:call-template>
+                    </xsl:attribute>
+                </xsl:if>
+                <xsl:if test="@svg:height">
+                    <xsl:attribute name="height">
+                        <xsl:call-template name="convert2pixel">
+                            <xsl:with-param name="value" select="@svg:height"/>
+                        </xsl:call-template>
+                    </xsl:attribute>
+                </xsl:if>
+                <xsl:if test="svg:desc">
+                    <xsl:attribute name="alt">
+                        <xsl:value-of select="svg:desc"/>
+                    </xsl:attribute>
+                </xsl:if>
+                <xsl:choose>
+                     <!-- for images jared in open office document -->
+                    <xsl:when test="contains(@xlink:href, '#Pictures/')">
+                        <!-- creating an absolute http URL to the packed image file -->
+                        <xsl:attribute name="src"><xsl:value-of select="concat($jaredRootURL, '/Pictures/', substring-after(@xlink:href, '#Pictures/'), $optionalURLSuffix)"/></xsl:attribute>
+                    </xsl:when>
+<!--                    Due to a XT bug no DOS ':' before DRIVE letter is allowed, it would result in a unkown protoco exception, but a file URL for a DOS
+                                               path needs the DRIVE letter, therefore all relative URLs remain relativ
+
+                                       <xsl:when test="contains(@xlink:href,'//') or (substring(@xlink:href,2,1) = ':') or starts-with(@xlink:href, '/')">
+                        <xsl:attribute name="src"><xsl:value-of select="@xlink:href"/></xsl:attribute>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <!~~ creating a absolute path/URL for the referenced resource ~~>
+                        <xsl:attribute name="src"><xsl:value-of select="concat($absoluteSourceDirRef, @xlink:href, $optionalURLSuffix)"/></xsl:attribute>
+                    </xsl:otherwise>
+-->
+                                       <xsl:otherwise>
+                        <xsl:attribute name="src"><xsl:value-of select="@xlink:href"/></xsl:attribute>
+                    </xsl:otherwise>
+                </xsl:choose>
+
+                <xsl:call-template name="apply-styles-and-content">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:element>
+            <!-- adding a line break to make the presentation more even with the OOo view -->
+            <xsl:element name="br"/>
+        </xsl:if>
+    </xsl:template>
+
+
+
+    <!-- ******************** -->
+    <!-- *** ordered list *** -->
+    <!-- ******************** -->
+
+    <xsl:template match="text:ordered-list">
+        <xsl:param name="collectedGlobalData"/>
+
+            <xsl:choose>
+                <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+                <xsl:when test="$outputType = 'CSS_HEADER'">
+                    <xsl:element name="ol">
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:element>
+                </xsl:when>
+                <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+                <xsl:when test="$outputType = 'CSS_INLINED'">
+                    <xsl:element name="ol">
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:element>
+                </xsl:when>
+                <!--+++++ PALM 3.2 SUBSET AND WAP INLINED WAY  +++++-->
+                <xsl:when test="$outputType = 'PALM'">
+                    <xsl:element name="ol">
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:element>
+                </xsl:when>
+                <!--+++++ WML / WAP  +++++-->
+                <xsl:otherwise>
+                    <xsl:choose>
+                        <!-- simulating content break of capsulated list elements -->
+                        <xsl:when test="ancestor::text:list-item">
+                            <xsl:choose>
+                                <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                                    <!-- simulating content break of capsulated list elements -->
+                                    <xsl:element name="br"></xsl:element>
+                                    <xsl:call-template name="apply-styles-and-content">
+                                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                    </xsl:call-template>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:element name="p">
+                                        <!-- simulating content break of capsulated list elements -->
+                                        <xsl:element name="br"></xsl:element>
+                                        <xsl:call-template name="apply-styles-and-content">
+                                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                        </xsl:call-template>
+                                    </xsl:element>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <xsl:call-template name="apply-styles-and-content">
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:call-template>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </xsl:otherwise>
+            </xsl:choose>
+    </xsl:template>
+
+
+
+    <!-- ********************** -->
+    <!-- *** unordered list *** -->
+    <!-- ********************** -->
+
+    <xsl:template match="text:unordered-list">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:choose>
+            <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+            <xsl:when test="$outputType = 'CSS_HEADER'">
+                <xsl:element name="ul">
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'CSS_INLINED'">
+                <xsl:element name="ul">
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ PALM 3.2 SUBSET AND WAP INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'PALM'">
+                <xsl:element name="ul">
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ WML / WAP  +++++-->
+            <xsl:otherwise>
+                <xsl:choose>
+                    <!-- simulating content break of capsulated list elements -->
+                    <xsl:when test="ancestor::text:list-item">
+                        <xsl:choose>
+                            <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                                <!-- simulating content break of capsulated list elements -->
+                                <xsl:element name="br"></xsl:element>
+                                <xsl:call-template name="apply-styles-and-content">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                            </xsl:when>
+                            <xsl:otherwise>
+                                <xsl:element name="p">
+                                    <!-- simulating content break of capsulated list elements -->
+                                    <xsl:element name="br"></xsl:element>
+                                    <xsl:call-template name="apply-styles-and-content">
+                                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                    </xsl:call-template>
+                                </xsl:element>
+                            </xsl:otherwise>
+                        </xsl:choose>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <!-- ****************** -->
+    <!-- *** list item  *** -->
+    <!-- ****************** -->
+
+    <xsl:template match="text:list-item">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:choose>
+            <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+            <xsl:when test="$outputType = 'CSS_HEADER'">
+                <xsl:element name="li">
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'CSS_INLINED'">
+                <xsl:element name="li">
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ PALM 3.2 SUBSET AND WAP INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'PALM'">
+                <xsl:element name="li">
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ WML / WAP  +++++-->
+            <xsl:otherwise>
+                <xsl:choose>
+                    <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                        <!-- simulating list elements -->
+                        <xsl:for-each select="ancestor::text:list-item">*</xsl:for-each>
+                        <xsl:text>* </xsl:text>
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                        <!-- list item break simulation (not in a table)-->
+                        <xsl:if test="not(ancestor::table:table-cell) or following-sibling::text:list-item">
+                            <xsl:element name="br"/>
+                        </xsl:if>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:element name="p">
+                            <!-- simulating list elements -->
+                            <xsl:for-each select="ancestor::text:list-item">*</xsl:for-each>
+                            <xsl:text>* </xsl:text>
+                            <xsl:call-template name="apply-styles-and-content">
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:call-template>
+                            <!-- list item break simulation (not in a table)-->
+                            <xsl:if test="not(ancestor::table:table-cell) or following-sibling::text:list-item">
+                                <xsl:element name="br"/>
+                            </xsl:if>
+                        </xsl:element>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <!-- ********************************************** -->
+    <!-- *** Text Section (contains: draw:text-box) *** -->
+    <!-- ********************************************** -->
+
+        <xsl:template match="text:section">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:if test="not(contains(@text:display, 'none'))">
+            <xsl:choose>
+                <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+                <xsl:when test="$outputType = 'CSS_HEADER'">
+                    <xsl:element name="span">
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:element>
+                </xsl:when>
+                <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+                <xsl:when test="$outputType = 'CSS_INLINED'">
+                    <xsl:element name="span">
+                        <xsl:call-template name="apply-styles-and-content">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:element>
+                </xsl:when>
+                <!--+++++ PALM 3.2 SUBSET INLINED WAY  +++++-->
+                <xsl:when test="$outputType = 'PALM'">
+                    <xsl:choose>
+                        <xsl:when test="name(parent::*) = 'text:list-item'">
+                                        <xsl:call-template name="apply-styles-and-content">
+                                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                        </xsl:call-template>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <xsl:element name="p">
+                                <xsl:call-template name="apply-styles-and-content">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                            </xsl:element>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </xsl:when>
+                <!--+++++ WML / WAP  +++++-->
+                <xsl:otherwise>
+                    <xsl:choose>
+                        <xsl:when test="not($outputType = 'WML')">
+                            <xsl:element name="a">
+                                <xsl:attribute name="href"><xsl:value-of select="@xlink:href"/></xsl:attribute>
+                                <xsl:call-template name="apply-styles-and-content">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                            </xsl:element>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <!-- no nested p tags in wml1.1 allowed -->
+                            <xsl:choose>
+                                <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                                    <xsl:call-template name="apply-styles-and-content">
+                                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                    </xsl:call-template>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:element name="p">
+                                        <xsl:call-template name="apply-styles-and-content">
+                                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                        </xsl:call-template>
+                                    </xsl:element>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:if>
+    </xsl:template>
+
+
+
+    <xsl:template match="text:line-break">
+        <xsl:element name="br"/>
+    </xsl:template>
+
+
+<!--
+    TABHANDLING PROBLEM: Tabs are possible to be shown in the HTML text file, but will be later stripped as whitespaces.
+        To prevent this one way would be the PRE tag which unfortunately ALWAYS result into a line-break. No surrounding NOBR tags help.
+
+    <xsl:template match="text:tab-stop">
+        <xsl:if test="not(preceding-sibling::text:tab-stop)">
+            <xsl:element name="pre"><xsl:text>&#9;</xsl:text><xsl:for-each select="following-sibling::text:tab-stop"><xsl:text>&#9;</xsl:text></xsl:for-each></xsl:element>
+        </xsl:if>
+     </xsl:template>
+
+    <xsl:template match="text:tab-stop"><xsl:text>&#9;</xsl:text></xsl:template>
+-->
+    <!-- HOTFIX: 8 non-breakable-spaces instead of a TAB is a hack sometimes less Tabs are needed and the code more difficult to read -->
+    <xsl:template match="text:tab-stop">
+        <xsl:call-template name="write-breakable-whitespace">
+            <xsl:with-param name="whitespaces" select="8"/>
+        </xsl:call-template>
+    </xsl:template>
+
+    <!-- currently there have to be an explicit call of the style attribute nodes, maybe the attributes nodes have no priority only order relevant-->
+    <!-- STRANGE: checked with biorythm.sxc a simple xsl:apply-templates did not recognice the styles. Maybe caused by the template match order?  -->
+    <xsl:template name="apply-styles-and-content">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:apply-templates select="@text:style-name | @draw:style-name | @draw:text-style-name | @table:style-name"><!-- | @presentation:style-name -->
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:apply-templates>
+
+        <xsl:apply-templates>
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:apply-templates>
+    </xsl:template>
+
+
+    <xsl:template match="@text:style-name | @draw:style-name | @draw:text-style-name | @table:style-name"><!-- | @presentation:style-name-->
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:choose>
+            <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+            <xsl:when test="$outputType = 'CSS_HEADER'">
+                <xsl:attribute name="class"><xsl:value-of select="translate(., '. %()/\', '')"/></xsl:attribute>
+            </xsl:when>
+
+            <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'CSS_INLINED'">
+                <xsl:attribute name="style"><xsl:value-of select="$collectedGlobalData/allstyles/*[name()=current()/.]"/></xsl:attribute>
+            </xsl:when>
+
+            <!--+++++ PALM 3.2 SUBSET INLINED WAY  and  WML / WAP   +++++-->
+            <xsl:when test="$outputType = 'PALM' or $outputType = 'WML'">
+                <!-- getting the css styles for the style name (mapped by style-mapping.xsl) -->
+                <xsl:variable name="styleProperties" select="$collectedGlobalData/allstyles/*[name()=current()/.]"/>
+                <!-- changing the context node -->
+                <xsl:for-each select="parent::*">
+                    <xsl:call-template name="create-nested-format-tags">
+                        <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:for-each>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <xsl:template match="text:sequence">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:apply-templates>
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:apply-templates>
+    </xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/global_document.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/global_document.xsl
new file mode 100644 (file)
index 0000000..fc4579e
--- /dev/null
@@ -0,0 +1,1674 @@
+ <!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                extension-element-prefixes="xt"
+                xmlns:urlencoder="http://www.jclark.com/xt/java/java.net.URLEncoder"
+                xmlns:sxghelper="http://www.jclark.com/xt/java/com.sun.star.xslt.helper.SxgChildTransformer"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+
+
+
+    <!-- ********************************************** -->
+    <!-- *** Global Document -  Table of Content    *** -->
+    <!-- ********************************************** -->
+
+
+
+    <xsl:template match="text:table-of-content">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:apply-templates>
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:apply-templates>
+    </xsl:template>
+
+
+
+    <xsl:template match="text:index-body">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:apply-templates mode="content-table">
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:apply-templates>
+    </xsl:template>
+
+
+
+    <xsl:template match="text:index-title" mode="content-table">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:apply-templates>
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:apply-templates>
+    </xsl:template>
+
+    <xsl:template match="text:reference-ref">
+        <xsl:param name="collectedGlobalData"/>
+
+        <!-- Java is needed as we have to encode the relative links (bug#102311) -->
+        <xsl:if test="not($isJavaDisabled)">
+            <xsl:element name="a">
+                <xsl:attribute name="href">
+                    <xsl:text>#</xsl:text>
+                    <xsl:call-template name="encode-string">
+                        <!-- the space has to be normalized,
+                            otherwise an illegal argument exception will be thrown for XT-->
+                         <xsl:with-param name="textToBeEncoded" select="@text:ref-name"/>
+                    </xsl:call-template>
+                </xsl:attribute>
+
+                <xsl:apply-templates>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:apply-templates>
+
+            </xsl:element>
+        </xsl:if>
+    </xsl:template>
+
+    <xsl:template match="text:reference-mark">
+        <xsl:param name="collectedGlobalData"/>
+
+        <!-- Java is needed as we have to encode the relative links (bug#102311) -->
+        <xsl:if test="not($isJavaDisabled)">
+            <xsl:element name="a">
+                <xsl:attribute name="name">
+                    <xsl:call-template name="encode-string">
+                        <!-- the space has to be normalized,
+                            otherwise an illegal argument exception will be thrown for XT-->
+                        <xsl:with-param name="textToBeEncoded" select="@text:name"/>
+                    </xsl:call-template>
+                </xsl:attribute>
+
+                <xsl:apply-templates>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:apply-templates>
+
+            </xsl:element>
+        </xsl:if>
+    </xsl:template>
+
+
+
+    <xsl:template match="text:reference-mark-start">
+        <xsl:param name="collectedGlobalData"/>
+
+        <!-- Java is needed as we have to encode the relative links (bug#102311) -->
+        <xsl:if test="not($isJavaDisabled)">
+            <xsl:element name="a">
+                <xsl:attribute name="name">
+                    <xsl:call-template name="encode-string">
+                        <!-- the space has to be normalized,
+                            otherwise an illegal argument exception will be thrown for XT-->
+                        <xsl:with-param name="textToBeEncoded" select="@text:name"/>
+                    </xsl:call-template>
+                </xsl:attribute>
+
+                <xsl:variable name="endOfReference">
+                    <xsl:for-each select="text:reference-mark-end[@name=current()/@text:name]">
+                        <xsl:value-of select="position()"/>
+                    </xsl:for-each>
+                </xsl:variable>
+
+                <xsl:for-each select="following-sibling::*[position() &lt; $endOfReference]">
+                    <xsl:apply-templates>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:apply-templates>
+                </xsl:for-each>
+            </xsl:element>
+       </xsl:if>
+    </xsl:template>
+
+
+
+
+     <!-- content table link  -->
+    <xsl:template match="text:a" mode="content-table">
+        <xsl:param name="collectedGlobalData"/>
+
+
+        <!-- For anchors in content-headers a bug exists (cp. bug id# 102311) and they have to be worked out separately.
+            Currently the link used in the content-table of an Office XML (e.g. in the content table as '#7.Some%20Example%20Headline%7Outline')
+            is not a valid URL (cp. bug id# 102311). No file destination is specified nor exist any anchor element for these
+            links in the Office XML, nor is the chapter no. known in the linked files.
+            A workaround for this transformation therefore had to be made. This time-consuming mechanism is disabled by default and
+            can be activated by a parameter (i.e. 'disableLinkedTableOfContent'). A creation of an anchor is made for each header element.
+            All header titles gonna be encoding to be usable in a relative URL. -->
+        <xsl:choose>
+            <xsl:when test="$disableLinkedTableOfContent or $isJavaDisabled">
+                <xsl:call-template name="create-common-link">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:call-template name="create-content-table-link">
+                    <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <xsl:template name="get-absolute-chapter-no">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="precedingChapterLevel1"/>
+
+        <xsl:choose>
+            <xsl:when test="$globalDocumentRefToCurrentFile">
+
+                <xsl:variable name="currentFileHeadingNo">
+                    <xsl:call-template name="get-current-file-heading-no"/>
+                </xsl:variable>
+                <xsl:variable name="testResult" select="$contentTableHeadings/heading[$globalDocumentRefToCurrentFile = @file-url][number($currentFileHeadingNo)]"/>
+
+                <xsl:call-template name="get-global-heading-no">
+                    <xsl:with-param name="currentFileHeadingNo" select="translate($testResult/@absolute-chapter-level, '+', '.')"/>
+                    <xsl:with-param name="precedingChapterLevel1" select="$precedingChapterLevel1"/>
+                </xsl:call-template>
+
+           </xsl:when>
+           <xsl:otherwise>
+                <!-- When the chapter is in the global document itself the link has to be relative (e.g. #index) a absolute href does not
+                    work with the browser. In case of chapter in the global document, the output URL of the global document was taken. -->
+                <xsl:variable name="currentFileHeadingNo">
+                    <xsl:call-template name="get-current-file-heading-no"/>
+                </xsl:variable>
+                <xsl:variable name="testResult" select="$collectedGlobalData/content-table-headings/heading[$contentTableURL = @file-url][number($currentFileHeadingNo)]"/>
+
+                <xsl:call-template name="get-global-heading-no">
+                    <xsl:with-param name="currentFileHeadingNo" select="translate($testResult/@absolute-chapter-level, '+', '.')"/>
+                    <xsl:with-param name="precedingChapterLevel1" select="$precedingChapterLevel1"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <xsl:template name="get-current-file-heading-no">
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:get-current-child-heading-no')">
+                <xsl:value-of select="sxghelper:get-current-child-heading-no()"/>
+            </xsl:when>
+            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.getCurrentChildHeadingNo')">
+                <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.getCurrentChildHeadingNo()"/>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <xsl:template name="get-next-current-file-heading-no">
+        <xsl:param name="file"/>
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:get-next-current-child-heading-no')">
+                <xsl:value-of select="sxghelper:get-next-current-child-heading-no($file)"/>
+            </xsl:when>
+            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.getNextCurrentChildHeadingNo')">
+                <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.getNextCurrentChildHeadingNo($file)"/>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <xsl:template name="get-global-heading-no">
+        <xsl:param name="currentFileHeadingNo"/>
+        <xsl:param name="precedingChapterLevel1"/>
+
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:get-global-heading-no')">
+                <xsl:value-of select="sxghelper:get-global-heading-no(string($currentFileHeadingNo), number($precedingChapterLevel1))"/>
+            </xsl:when>
+            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.getGlobalHeadingNo')">
+                <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.getGlobalHeadingNo(string($currentFileHeadingNo), number($precedingChapterLevel1))"/>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+
+    <!-- necessary as anchor for the content table -->
+    <xsl:template name="create-heading-anchor">
+        <xsl:param name="collectedGlobalData"/>
+
+        <!--
+        Currently the link used in the Office XML (e.g. in the content table as '#7.Some%20Example%20Headline%7Outline')
+        is not a valid URL (cmp. bug id# 102311). No file destination is specified nor exist any anchor element for these
+        links in the Office XML.
+        Here we are creating an anchor with the space normalized text of this header as potential jump address of the content table -->
+
+        <xsl:choose>
+            <xsl:when test="$globalDocumentRefToCurrentFile">
+
+                <xsl:variable name="currentFileHeadingNo">
+                    <xsl:call-template name="get-next-current-file-heading-no">
+                         <xsl:with-param name="file" select="$globalDocumentRefToCurrentFile"/>
+                    </xsl:call-template>
+                </xsl:variable>
+
+
+                <xsl:variable name="testResult" select="$contentTableHeadings/heading[$globalDocumentRefToCurrentFile = @file-url][number($currentFileHeadingNo)]"/>
+                <xsl:if test="$isDebugMode">
+                    <xsl:message>Matching child document header No. <xsl:value-of select="$currentFileHeadingNo"/></xsl:message>
+                    <xsl:message>absolute-chapter-level:         <xsl:value-of select="$testResult/@absolute-chapter-level"/></xsl:message>
+                    <xsl:message>encodedTitle:                   <xsl:value-of select="$testResult/@encoded-title"/></xsl:message>
+                    <xsl:message>globalDocumentRefToCurrentFile: <xsl:value-of select="$globalDocumentRefToCurrentFile"/></xsl:message>
+                    <xsl:message>*** </xsl:message>
+                </xsl:if>
+
+                <xsl:element name="a">
+                    <xsl:attribute name="name">
+                        <xsl:value-of select="$testResult/@absolute-chapter-level"/>
+                        <xsl:text>+</xsl:text>
+                        <xsl:value-of select="$testResult/@encoded-title"/>
+                    </xsl:attribute>
+                </xsl:element>
+           </xsl:when>
+
+           <xsl:otherwise>
+                <!-- When the chapter is in the global document itself the link has to be relative (e.g. #index) a absolute href does not
+                    work with the browser. In case of chapter in the global document, the output URL of the global document was taken. -->
+                <xsl:variable name="currentFileHeadingNo">
+                    <xsl:call-template name="get-next-current-file-heading-no">
+                         <xsl:with-param name="file" select="$contentTableURL"/>
+                    </xsl:call-template>
+                </xsl:variable>
+
+
+                <xsl:variable name="testResult" select="$collectedGlobalData/content-table-headings/heading[$contentTableURL = @file-url][number($currentFileHeadingNo)]"/>
+
+                <xsl:if test="$isDebugMode">
+                    <xsl:message>Matching global document header No. <xsl:value-of select="$currentFileHeadingNo"/></xsl:message>
+                    <xsl:message>absolute-chapter-level:  <xsl:value-of select="$testResult/@absolute-chapter-level"/></xsl:message>
+                    <xsl:message>encodedTitle:            <xsl:value-of select="$testResult/@encoded-title"/></xsl:message>
+                    <xsl:message>contentTableURL:         <xsl:value-of select="$contentTableURL"/></xsl:message>
+                    <xsl:message>*** </xsl:message>
+                </xsl:if>
+
+                <xsl:element name="a">
+                    <xsl:attribute name="name">
+                        <xsl:value-of select="$testResult/@absolute-chapter-level"/>
+                        <xsl:text>+</xsl:text>
+                        <xsl:value-of select="$testResult/@encoded-title"/>
+                    </xsl:attribute>
+                </xsl:element>
+
+            </xsl:otherwise>
+        </xsl:choose>
+
+
+
+<!--
+
+        <xsl:variable name="title" select="normalize-space(.)"/>
+        <!~~DON'T WORK    <xsl:variable name="title" select="normalize-space(descendant::text())"/>        ~~>
+         <xsl:choose>
+            <xsl:when test="$globalDocumentRefToCurrentFile">
+                <xsl:variable name="testResults" select="$contentTableHeadings/heading[$globalDocumentRefToCurrentFile = @file-url][$title = @title][current()/@text:level = @level]"/>
+                <xsl:if test="1 &lt; count($testResults)">
+                    <xsl:message> *** CAUTION: Multiple chapter headings with similar names: </xsl:message>
+                    <xsl:message> *** Title: <xsl:value-of select="$title"/> Level: <xsl:value-of select="@text:level"/></xsl:message>
+                </xsl:if>
+
+                 <xsl:variable name="encodedTitle" select="$testResults/@encoded-title"/>
+                 <xsl:choose>
+                     <xsl:when test="$encodedTitle">
+                         <xsl:element name="a">
+                            <xsl:attribute name="name">
+                                <xsl:value-of select="$encodedTitle"/>
+                            </xsl:attribute>
+                         </xsl:element>
+                     </xsl:when>
+                     <xsl:otherwise>
+                        <!~~ even when it is not ~~>
+                        <xsl:variable name="newEncodedTitle">
+                            <xsl:call-template name="encode-string">
+                                <!~~ the space has to be normalized,
+                                    otherwise an illegal argument exception will be thrown for XT~~>
+                                 <xsl:with-param name="textToBeEncoded" select="$title"/>
+                            </xsl:call-template>
+                        </xsl:variable>
+                         <xsl:element name="a">
+                            <xsl:attribute name="name">
+                                <xsl:value-of select="$newEncodedTitle"/>
+                            </xsl:attribute>
+                         </xsl:element>
+                     </xsl:otherwise>
+                 </xsl:choose>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:variable name="testResults" select="$collectedGlobalData/content-table-headings/heading[$contentTableURL = @file-url][$title = @title][current()/@text:level = @level]"/>
+                <xsl:if test="1 &lt; count($testResults)">
+                    <xsl:message> *** CAUTION: Multiple chapter headings with similar names: </xsl:message>
+                    <xsl:message> *** Title: <xsl:value-of select="$title"/> Level: <xsl:value-of select="@text:level"/></xsl:message>
+                    <xsl:message> *** </xsl:message>
+                </xsl:if>
+
+                <xsl:variable name="encodedTitle" select="$testResults/@encoded-title"/>
+                <xsl:choose>
+                     <xsl:when test="$encodedTitle">
+                         <xsl:element name="a">
+                            <xsl:attribute name="name">
+                                <xsl:value-of select="$encodedTitle"/>
+                            </xsl:attribute>
+                         </xsl:element>
+                     </xsl:when>
+                     <xsl:otherwise>
+                        <!~~ even when it is not ~~>
+                        <xsl:variable name="newEncodedTitle">
+                            <xsl:call-template name="encode-string">
+                                <!~~ the space has to be normalized,
+                                    otherwise an illegal argument exception will be thrown for XT~~>
+                                 <xsl:with-param name="textToBeEncoded" select="$title"/>
+                            </xsl:call-template>
+                        </xsl:variable>
+                         <xsl:element name="a">
+                            <xsl:attribute name="name">
+                                <xsl:value-of select="$newEncodedTitle"/>
+                            </xsl:attribute>
+                         </xsl:element>
+                     </xsl:otherwise>
+                 </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+
+-->
+
+    </xsl:template>
+
+
+
+
+    <!-- ************************************** -->
+    <!--    CREATION OF A CONTENT TABLE LINK    -->
+    <!-- ************************************** -->
+
+
+    <!-- a special behavior of text:a
+        (called from the 'text:a' template) -->
+
+    <xsl:template name="create-content-table-link">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:choose>
+            <xsl:when test="not($outputType = 'WML')">
+                <xsl:element name="a">
+                    <xsl:attribute name="href">
+                        <xsl:choose>
+                            <xsl:when test="starts-with(@xlink:href, '#')">
+                                <xsl:variable name="correctHeading" select="$collectedGlobalData/content-table-headings/heading[current()/@xlink:href = @content-table-id]"/>
+
+                                <xsl:value-of select="$correctHeading/@out-file-url"/>
+                                <xsl:text>#</xsl:text>
+                                <xsl:value-of select="$correctHeading/@absolute-chapter-level"/>
+                                <xsl:text>+</xsl:text>
+                                <xsl:value-of select="$correctHeading/@encoded-title"/>
+                            </xsl:when>
+                            <xsl:otherwise>
+                                <xsl:call-template name="create-common-link">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                            </xsl:otherwise>
+                        </xsl:choose>
+                    </xsl:attribute>
+
+                    <xsl:call-template name="apply-styles-and-content">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+            <xsl:otherwise>
+                <!-- 2DO: currently no WML support
+
+                <!~~ no nested p tags in wml1.1 allowed ~~>
+                <xsl:choose>
+                    <xsl:when test="ancestor::*[contains($wap-paragraph-elements, name())]">
+                        <xsl:element name="a">
+                            <xsl:attribute name="href"><xsl:value-of select="@xlink:href"/></xsl:attribute>
+                            <xsl:apply-templates select="."/>
+                        </xsl:element>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:element name="p">
+                            <xsl:element name="a">
+                                <xsl:attribute name="href"><xsl:value-of select="@xlink:href"/></xsl:attribute>
+                                <xsl:apply-templates select="."/>
+                            </xsl:element>
+                        </xsl:element>
+                    </xsl:otherwise>
+                </xsl:choose>  -->
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+       <!--
+           CREATION OF A HELPER VARIABLE AS WORKAROUND FOR THE CONTENT TABLE ULR BUG
+
+
+        As no valid URL from the content table to the child documents exist in the content table,
+        a work-around is done:
+
+        First two helper variables are being created.
+
+        One containing the list of all references of the global document:
+        containg all their title,
+        for example:
+
+               <chapter-ref title="aTitle 1"/>
+               <chapter-ref title="aTitle 2"/>
+               <chapter-ref title="aTitle 2/>
+               <chapter-ref title="aTitle 3/>
+
+        The other containing all heading from the child documents linked from the global document.
+        The variable 'childrenHeadings' contains their title and the number of preceding similar titles,
+        for example:
+
+
+               <child file-url="aURL">
+                       <heading title="aTitle1" level="1">
+                       <heading title="aTitle2" level="2">
+                       <heading title="aTitle3" level="1">
+            </child>
+
+        For each chapter reference from the content table the
+
+         by encoding the chapter names of the child document with the java URLEncoder and
+        use this as a part of a link. Furthermore for all heading elements a encoded anchor will be created from the heading.
+        Last the workaround parses all children documents for this anhor, as there is no distinction of files from the content table entries.
+
+        The new added node set to the collectedGlobalData variable concering the content table is written as
+
+
+               <content-table-headings content-table-url="aURL_ToTheGeneratedContentTable">
+               <heading file-url="aFileURLToTheGeneratedHeading1" level="1">
+               <heading file-url="aFileURLToTheGeneratedHeading2" level="2">
+               <heading file-url="aFileURLToTheGeneratedHeading1" level="1">
+               <heading file-url="aFileURLToTheGeneratedHeading2" level="2">
+               </content-table-headings>
+
+
+        Preconditions:
+               The correct sequence of child documents according to the Content Table is necessary, granted by the office.
+       -->
+       <xsl:template name="Create-helper-variables-for-Content-Table">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:if test="$isDebugMode"><xsl:message>Creation of global document helper variable for the content table....</xsl:message></xsl:if>
+
+        <!-- Here a helper variable of the content table is created, of all chapter-references which point to a child document.
+             an 'chapter-ref' element will be created, containg their title and the number of preceding similar titles,
+             for example:
+
+               <chapter-ref title="aTitle 1"/>
+               <chapter-ref title="aTitle 2"/>
+               <chapter-ref title="aTitle 2"/>
+               <chapter-ref title="aTitle 3"/>
+            -->
+        <xsl:variable name="chapterRefs-RTF">
+            <!-- '/*/' as the flat and the zipped XML file format have different root elements -->
+            <xsl:for-each select="/*/office:body/text:table-of-content/text:index-body/text:p/text:a">
+                <xsl:variable name="currentTitle" select="normalize-space(string(.))"/>
+                <xsl:element name="chapter-ref">
+                    <xsl:attribute name="title">
+                        <xsl:value-of select="$currentTitle"/>
+                    </xsl:attribute>
+                    <xsl:attribute name="content-table-id">
+                        <xsl:value-of select="@xlink:href"/>
+                    </xsl:attribute>
+               </xsl:element>
+            </xsl:for-each>
+        </xsl:variable>
+        <xsl:if test="$isDebugMode"><xsl:message>Finished the Creation of global document helper variable for the content table!</xsl:message></xsl:if>
+
+
+
+
+        <xsl:if test="$isDebugMode"><xsl:message>Creation of global document helper variable for the child documents....</xsl:message></xsl:if>
+        <!-- Here a helper variable of created from the children documents.
+             Containg all heading elements from the child documents. Some or all of them are
+             chapters referenced by the Global Document.
+             The variable contains their title, the level of the heading and the file URL of the child,
+             for example:
+
+                       <heading title="aTitle1" level="1" file-url="aURL1">
+                       <heading title="aTitle2" level="2" file-url="aURL1">
+                       <heading title="aTitle3" level="1" file-url="aURL1">
+                       <heading title="aTitle4" level="1" file-url="aURL2">
+                       <heading title="aTitle5" level="2" file-url="aURL2">
+                       <heading title="aTitle2" level="3" file-url="aURL2">
+                       <heading title="aTitle6" level="3" file-url="aURL2">
+                <heading-count>7</heading-count>
+            -->
+        <xsl:variable name="childrenHeadings-RTF">
+            <!-- all headers from children documents will be added -->
+            <xsl:apply-templates select="/*/office:body/text:section" mode="creation-of-variable"/>
+        </xsl:variable>
+        <xsl:if test="$isDebugMode"><xsl:message>Finished the Creation of global document helper variable for the child documents!</xsl:message></xsl:if>
+
+
+        <xsl:choose>
+            <xsl:when test="function-available('xt:node-set')">
+                <xsl:call-template name="Create-global-variable-for-Content-Table">
+                    <xsl:with-param name="chapterRefs"     select="xt:node-set($chapterRefs-RTF)"/>
+                    <xsl:with-param name="childrenHeadings"     select="xt:node-set($childrenHeadings-RTF)"/>
+                    <xsl:with-param name="collectedGlobalData"  select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="function-available('xalan:nodeset')">
+                <xsl:call-template name="Create-global-variable-for-Content-Table">
+                    <xsl:with-param name="chapterRefs"     select="xalan:nodeset($chapterRefs-RTF)"/>
+                    <xsl:with-param name="childrenHeadings"     select="xalan:nodeset($childrenHeadings-RTF)"/>
+                    <xsl:with-param name="collectedGlobalData"  select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+
+    <xsl:template name="Create-global-variable-for-Content-Table">
+        <xsl:param name="chapterRefs"/>
+        <xsl:param name="childrenHeadings"/>
+        <xsl:param name="collectedGlobalData"/>
+
+
+        <xsl:if test="$isDebugMode">
+            <!-- helper variable collecting all headings from the global document file children-->
+            <xsl:for-each select="$childrenHeadings/heading">
+                <xsl:message>#              <xsl:value-of select="position()"/></xsl:message>
+                <xsl:message>level:         <xsl:value-of select="@level"/></xsl:message>
+                <xsl:message>title:         <xsl:value-of select="@title"/></xsl:message>
+                <xsl:message>encoded-title: <xsl:value-of select="@encoded-title"/></xsl:message>
+                <xsl:message>file-url:      <xsl:value-of select="@file-url"/></xsl:message>
+                <xsl:message>header-no:     <xsl:value-of select="@header-no"/></xsl:message>
+                <xsl:message>**</xsl:message>
+            </xsl:for-each>
+            <xsl:message>**</xsl:message>
+            <xsl:message>**</xsl:message>
+
+            <!-- helper variable collecting all heading references from the content table of the the global document -->
+            <xsl:message>childrenHeadings/heading-count: <xsl:value-of select="$childrenHeadings/heading-count"/></xsl:message>
+            <xsl:for-each select="$chapterRefs/chapter-ref">
+                <xsl:message># <xsl:value-of select="position()"/></xsl:message>
+                <xsl:message>title: <xsl:value-of select="@title"/></xsl:message>
+                <xsl:message>**</xsl:message>
+            </xsl:for-each>
+        </xsl:if>
+
+
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:set-heading-no')">
+                    <xsl:value-of select="sxghelper:set-heading-no(1)"/>
+                    <xsl:value-of select="sxghelper:set-current-child-no(1)"/>
+                    <xsl:value-of select="sxghelper:set-current-child-url(string($childrenHeadings/heading/@file-url))"/>
+            </xsl:when>
+            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.setHeadingNo')">
+                    <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.setHeadingNo(1)"/>
+                    <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.setCurrentChildNo(1)"/>
+                    <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.setCurrentChildUrl(string($childrenHeadings/heading/@file-ur))"/>
+            </xsl:when>
+        </xsl:choose>
+
+        <xsl:if test="$isDebugMode"><xsl:message>Creating global document variable for chapter relations....</xsl:message></xsl:if>
+        <xsl:variable name="contentTableHeadingsGlobalData-RTF">
+            <xsl:element name="content-table-headings">
+                <!-- all headings are linked from the current global document input file -->
+                <xsl:attribute name="content-table-url">
+                    <xsl:value-of select="$contentTableURL"/>
+                </xsl:attribute>
+
+                <!-- had to use a for loop, as a recursion ends with an stackoverflow exception after about 600 recursive calls -->
+                <xsl:choose>
+                    <xsl:when test="function-available('sxghelper:get-heading-no')">
+                         <xsl:for-each select="$chapterRefs/chapter-ref">
+                            <xsl:call-template name="searchHeadingInChildDocument">
+                                <xsl:with-param name="chapterRefs"         select="$chapterRefs"/>
+                                <xsl:with-param name="childrenHeadings"    select="$childrenHeadings"/>
+                                <xsl:with-param name="currentChapterRefNo" select="position()"/>
+                                <xsl:with-param name="currentHeadingNo"    select="sxghelper:get-heading-no()"/>
+                                <xsl:with-param name="currentChildURL"     select="sxghelper:get-current-child-url()"/>
+                                <xsl:with-param name="currentChildNo"      select="sxghelper:get-current-child-no()"/>
+                            </xsl:call-template>
+                        </xsl:for-each>
+                     </xsl:when>
+                    <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.getHeadingNo')">
+                         <xsl:for-each select="$chapterRefs/chapter-ref">
+                            <xsl:call-template name="searchHeadingInChildDocument">
+                                <xsl:with-param name="chapterRefs"         select="$chapterRefs"/>
+                                <xsl:with-param name="childrenHeadings"    select="$childrenHeadings"/>
+                                <xsl:with-param name="currentChapterRefNo" select="position()"/>
+                                <xsl:with-param name="currentHeadingNo"    select="java:com.sun.star.xslt.helper.SxgChildTransformer.getHeadingNo()"/>
+                                <xsl:with-param name="currentChildURL"     select="java:com.sun.star.xslt.helper.SxgChildTransformer.getCurrentChildUrl()"/>
+                                <xsl:with-param name="currentChildNo"      select="java:com.sun.star.xslt.helper.SxgChildTransformer.getCurrentChildNo()"/>
+                            </xsl:call-template>
+                        </xsl:for-each>
+                    </xsl:when>
+                </xsl:choose>
+            </xsl:element>
+
+            <!-- adding the already exisiting global data environment -->
+            <xsl:copy-of select="$collectedGlobalData"/>
+        </xsl:variable>
+        <xsl:if test="$isDebugMode"><xsl:message>Finished global document variable for chapter relations!</xsl:message></xsl:if>
+
+        <xsl:choose>
+            <xsl:when test="function-available('xt:node-set')">
+                <xsl:call-template name="start-self-and-children-transformation">
+                    <xsl:with-param name="collectedGlobalData"       select="xt:node-set($contentTableHeadingsGlobalData-RTF)"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="function-available('xalan:nodeset')">
+                <xsl:call-template name="start-self-and-children-transformation">
+                    <xsl:with-param name="collectedGlobalData"       select="xalan:nodeset($contentTableHeadingsGlobalData-RTF)"/>
+                </xsl:call-template>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <xsl:template name="searchHeadingInChildDocument">
+        <xsl:param name="chapterRefs"/>
+        <xsl:param name="childrenHeadings"/>
+        <xsl:param name="currentChapterRefNo"/>
+        <xsl:param name="currentHeadingNo"/>
+        <xsl:param name="currentChildURL"/>
+        <xsl:param name="currentChildNo"/>
+
+
+        <xsl:variable name="currentChapterRef"      select="$chapterRefs/chapter-ref[$currentChapterRefNo]"/>
+        <xsl:variable name="currentChapterID"       select="$currentChapterRef/@content-table-id"/>
+        <xsl:variable name="currentChapterTitle"    select="$currentChapterRef/@title"/>
+
+        <xsl:variable name="currentChildHeading"    select="$childrenHeadings/heading[$currentHeadingNo]"/>
+        <xsl:variable name="headingTitle"           select="$currentChildHeading/@title"/>
+        <xsl:variable name="headingLevel"           select="$currentChildHeading/@level"/>
+        <xsl:variable name="headingNo"              select="$currentChildHeading/@header-no"/>
+        <xsl:variable name="newChildURL"            select="$currentChildHeading/@file-url"/>
+
+        <xsl:if test="$isDebugMode">
+            <xsl:message>*** new heading </xsl:message>
+            <xsl:message>currentChapterID:    <xsl:value-of select="$currentChapterID"/></xsl:message>
+            <xsl:message>currentChapterTitle: <xsl:value-of select="$currentChapterTitle"/></xsl:message>
+            <xsl:message>currentChapterID:    <xsl:value-of select="$currentChapterID"/></xsl:message>
+            <xsl:message>currentHeadingNo:    <xsl:value-of select="$currentHeadingNo"/></xsl:message>
+            <xsl:message>headingTitle:        <xsl:value-of select="$headingTitle"/></xsl:message>
+            <xsl:message>headingLevel:        <xsl:value-of select="$headingLevel"/></xsl:message>
+            <xsl:message>headingNo:           <xsl:value-of select="$headingNo"/></xsl:message>
+            <xsl:message>newChildURL:         <xsl:value-of select="$newChildURL"/></xsl:message>
+        </xsl:if>
+
+        <xsl:variable name="outFileURL">
+            <xsl:choose>
+                 <xsl:when test="substring-before($newChildURL,'.xml')">
+                    <xsl:value-of select="concat(substring-before($newChildURL,'.xml'),'.htm')"/>
+                 </xsl:when>
+                 <xsl:when test="substring-before($newChildURL,'.sx')">
+                    <xsl:value-of select="concat(substring-before($newChildURL,'.sx'),'.htm')"/>
+                 </xsl:when>
+            </xsl:choose>
+        </xsl:variable>
+        <xsl:variable name="isChapterHeading" select="$headingTitle = $currentChapterTitle"/>
+        <xsl:variable name="isNewFile" select="string($newChildURL) != string($currentChildURL)"/>
+
+
+
+
+        <xsl:if test="$isNewFile">
+            <!-- reset of the already collected child headers -->
+            <xsl:call-template name="calc-chapter-numbers">
+                 <xsl:with-param name="level" select="0"/>
+            </xsl:call-template>
+        </xsl:if>
+        <xsl:variable name="absoluteChapterLevel">
+            <xsl:call-template name="calc-chapter-numbers">
+                 <xsl:with-param name="level" select="number($headingLevel)"/>
+            </xsl:call-template>
+        </xsl:variable>
+
+
+        <xsl:element name="heading">
+            <!-- necessary to as ID from the content table to get the correct heading element (the buggy URL used as ID)-->
+            <xsl:attribute name="content-table-id">
+                <xsl:choose>
+                    <xsl:when test="$isChapterHeading">
+                        <xsl:value-of select="$currentChapterID"/>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:text>only a heading, but not a chapter</xsl:text>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:attribute>
+            <!-- no of the used child, necessary for quick finding of chapters of next file  -->
+            <xsl:attribute name="child-document-no">
+                <xsl:choose>
+                    <xsl:when test="$isNewFile">
+                        <xsl:value-of select="$currentChildNo + 1"/>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:value-of select="$currentChildNo"/>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:attribute>
+            <!-- the URL of the child document source, containing the heading -->
+            <xsl:attribute name="file-url">
+                <xsl:value-of select="$newChildURL"/>
+            </xsl:attribute>
+            <xsl:attribute name="out-file-url">
+                <xsl:value-of select="$outFileURL"/>
+            </xsl:attribute>
+            <xsl:attribute name="level">
+                <xsl:value-of select="$headingLevel"/>
+            </xsl:attribute>
+            <xsl:attribute name="title">
+                <xsl:value-of select="$headingTitle"/>
+            </xsl:attribute>
+            <xsl:attribute name="encoded-title">
+                <xsl:value-of select="$currentChildHeading/@encoded-title"/>
+            </xsl:attribute>
+            <xsl:attribute name="absolute-chapter-level">
+                <xsl:value-of select="$absoluteChapterLevel"/>
+            </xsl:attribute>
+        </xsl:element>
+
+
+        <xsl:choose>
+            <xsl:when test="$childrenHeadings/heading-count != $currentHeadingNo">
+                <!-- procede as long the list of children isn'nt worked through -->
+                <xsl:choose>
+                    <xsl:when test="$isChapterHeading">
+                        <!-- global variables have to be set, so the for-each loop can access them -->
+                        <xsl:choose>
+                            <xsl:when test="function-available('sxghelper:set-heading-no')">
+                                <xsl:value-of select="sxghelper:set-heading-no($currentHeadingNo + 1)"/>
+                                <xsl:if test="$isNewFile">
+                                    <xsl:value-of select="sxghelper:set-current-child-no($currentChildNo + 1)"/>
+                                    <xsl:value-of select="sxghelper:set-current-child-url(string($newChildURL))"/>
+                                </xsl:if>
+                             </xsl:when>
+                            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.setHeadingNo')">
+                                <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.setHeadingNo($currentHeadingNo + 1)"/>
+                                <xsl:if test="$isNewFile">
+                                    <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.setCurrentChildNo($currentChildNo + 1)"/>
+                                    <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.setCurrentChildUrl($newChildURL)"/>
+                                </xsl:if>
+                            </xsl:when>
+                        </xsl:choose>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <!-- not a chapter heading, call itself until a chapter ref is found or the end of headings is reached -->
+                        <xsl:call-template name="searchHeadingInChildDocument">
+                            <xsl:with-param name="chapterRefs"         select="$chapterRefs"/>
+                            <xsl:with-param name="childrenHeadings"    select="$childrenHeadings"/>
+                            <xsl:with-param name="currentChapterRefNo" select="$currentChapterRefNo"/>
+                            <xsl:with-param name="currentHeadingNo"    select="$currentHeadingNo + 1"/>
+                            <xsl:with-param name="currentChildURL"     select="$currentChildURL"/>
+                            <xsl:with-param name="currentChildNo"      select="$currentChildNo"/>
+                        </xsl:call-template>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:if test="$isDebugMode">
+                    <xsl:message>All child documents have been walked through without finding the chapter name!</xsl:message>
+                    <xsl:message>       childrenHeadings/heading-count:    <xsl:value-of select="$childrenHeadings/heading-count"/></xsl:message>
+                    <xsl:message>       currentHeadingNo:                  <xsl:value-of select="$currentHeadingNo"/></xsl:message>
+                </xsl:if>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+
+
+       <!-- Chapters from the Content Table have currently no anchor to child documents in OOo XML.
+                As solution, whenever a a global document  every header of the HTML output gets get's an anchor in the Therefore-->
+    <xsl:template name="encode-string">
+        <xsl:param name="encoding" select="'UTF-8'"/>
+        <xsl:param name="textToBeEncoded"/>
+
+        <xsl:choose>
+            <xsl:when test="function-available('urlencoder:encode')">
+                <xsl:value-of select="urlencoder:encode(normalize-space($textToBeEncoded),$encoding)"/>
+            </xsl:when>
+            <xsl:when test="function-available('java:java.net.URLEncoder.encode')">
+                <xsl:value-of select="java:java.net.URLEncoder.encode(string(normalize-space($textToBeEncoded)),string($encoding))"/>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+
+
+    <!-- ******************************************************************************************************** -->
+    <!-- ***  TRANSFORMATION OF ALL CHILD DOCUMENTS OF THE GLOBAL DOCUMENTS BY USING A EXTERNAL HELPER CLASS  *** -->
+    <!-- ******************************************************************************************************** -->
+
+
+       <!-- a new element 'contentTableHeadings' will be added to the helper variable the first time a child will be transformed -->
+    <xsl:template name="transform-global-document-and-children">
+        <xsl:param name="collectedGlobalData"/>
+
+
+        <xsl:choose>
+            <xsl:when test="$collectedGlobalData/content-table-headings">
+                <xsl:call-template name="start-child-transformation">
+                    <xsl:with-param name="collectedGlobalData"   select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:otherwise>
+                   <!-- The necessary auxiliary variable hasn't build yet.
+                   This variable gonna store all headers (with chapter numbers) and the URL of their files -->
+
+                <xsl:call-template name="Create-helper-variables-for-Content-Table">
+                    <xsl:with-param name="collectedGlobalData"   select="$collectedGlobalData"/>
+                </xsl:call-template>
+
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+       <xsl:template name="start-self-and-children-transformation">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:if test="$isDebugMode">
+            <xsl:call-template name="debug-content-table-headings-variable">
+                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+            </xsl:call-template>
+
+            <xsl:message>Parsing the global document...</xsl:message>
+        </xsl:if>
+
+        <xsl:apply-templates>
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:apply-templates>
+
+
+        <xsl:if test="$isDebugMode"><xsl:message>Parsing the child documents...</xsl:message></xsl:if>
+        <xsl:call-template name="start-child-transformation">
+            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+        </xsl:call-template>
+
+       </xsl:template>
+
+
+
+
+    <xsl:template name="start-child-transformation">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:if test="$isDebugMode"><xsl:message>Starting the child transformations...</xsl:message></xsl:if>
+
+        <!-- As the childs of a global document (with suffix .sxg) do not know anything about their global parent,
+            the transformation of global documents children have to be done implizit.
+            Otherwise the chapter number of the children will always start with zero, as they do not know anything about the
+            proceding chapters.
+            Furthermore, they don't have any links about preceeding and following documents and no linking for usability reasons
+            could be done. Therefore the children have to be transformed during the transformation of a global (sxg) document -->
+               <xsl:if test="$isDebugMode">
+            <xsl:choose>
+                <xsl:when test="$collectedGlobalData/content-table-headings">
+                    <xsl:message>Contentable data exists as global data!</xsl:message>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:message>No Contentable global data exists!</xsl:message>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:if>
+
+        <!-- currently this function only works with node-sets from XT -->
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:transform-children')">
+                <xsl:message>
+                    <xsl:value-of select="sxghelper:transform-children( $collectedGlobalData/content-table-headings,
+                                                                        string($jaredRootURL),
+                                                                        string($absoluteSourceDirRef),
+                                                                        string($optionalURLSuffix),
+                                                                        string($dpi),
+                                                                        string($outputType),
+                                                                        $isDebugMode)"/>
+                </xsl:message>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:message>Java method transformChildren to transform all children of a global document could not be found. Be sure to use the XT processor.</xsl:message>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+
+
+    <!-- ******************************************************************************* -->
+    <!-- ***  Creation of helper variable of the headings of all children documents  *** -->
+    <!-- ******************************************************************************* -->
+
+
+    <xsl:template match="/*/office:body/text:section" mode="creation-of-variable">
+        <xsl:call-template name="getChildRootNode"/>
+
+        <!-- after the last child document the global document will be parsed -->
+        <xsl:if test="position() = last()">
+            <!-- search the global document after all child documents have been searched
+
+ODK PATCH NO INDEX ELEMENT WANTED !! - null pointer exception
+            <xsl:call-template name="getPreviousHeaderNo">
+                <xsl:with-param name="fileURL"                  select="$contentTableURL"/>
+                <xsl:with-param name="amountOfCurrentHeading"   select="count(following-sibling::text:h)"/>
+                <xsl:with-param name="nodeToSearchForHeading"   select="following-sibling::text:h"/>
+            </xsl:call-template>
+-->
+           <!-- get the overall No of Headers -->
+           <xsl:call-template name="getAllHeaderNo"/>
+        </xsl:if>
+    </xsl:template>
+
+
+    <xsl:template name="getChildRootNode">
+        <xsl:variable name="fileURL"    select="text:section-source/@xlink:href"/>
+
+        <xsl:choose>
+               <!-- if absolute URL or absolute DOS PATH or absolute Unix path -->
+                       <xsl:when test="contains($fileURL,'//') or (substring($fileURL,2,1) = ':') or starts-with($fileURL, '/')">
+                       <xsl:variable name="childRootNode" select="document($fileURL)"/>
+                <xsl:call-template name="getPreviousHeaderNo">
+                    <xsl:with-param name="fileURL"                select="$fileURL"/>
+                       <!-- NO absolute source path will be added as prefix -->
+                    <xsl:with-param name="amountOfCurrentHeading" select="count($childRootNode/*/office:body/descendant::text:h)"/>
+                    <xsl:with-param name="nodeToSearchForHeading" select="$childRootNode/*/office:body/descendant::text:h"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:otherwise>
+                       <xsl:variable name="childRootNode" select="document(concat($absoluteSourceDirRef,'/',$fileURL))"/>
+                <xsl:call-template name="getPreviousHeaderNo">
+                    <xsl:with-param name="fileURL"                select="$fileURL"/>
+                       <!-- the absolute source path will be added as prefix -->
+                    <xsl:with-param name="amountOfCurrentHeading" select="count($childRootNode/*/office:body/descendant::text:h)"/>
+                    <xsl:with-param name="nodeToSearchForHeading" select="$childRootNode/*/office:body/descendant::text:h"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <xsl:template name="getPreviousHeaderNo">
+        <xsl:param name="fileURL"/>
+        <xsl:param name="nodeToSearchForHeading"/>
+        <xsl:param name="amountOfCurrentHeading"/>
+
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:get-previous-child-documents-heading-count')">
+                <xsl:call-template name="addHeadingInfo">
+                    <xsl:with-param name="nodeToSearchForHeading"   select="$nodeToSearchForHeading"/>
+                    <xsl:with-param name="fileURL"                  select="$fileURL"/>
+                    <xsl:with-param name="previousHeader"           select="sxghelper:get-previous-child-documents-heading-count($amountOfCurrentHeading)"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.getPreviousChildDocumentsHeadingCount')">
+                <xsl:call-template name="addHeadingInfo">
+                    <xsl:with-param name="nodeToSearchForHeading"   select="$nodeToSearchForHeading"/>
+                    <xsl:with-param name="fileURL"                  select="$fileURL"/>
+                    <xsl:with-param name="previousHeader"           select="java:com.sun.star.xslt.helper.SxgChildTransformer.getPreviousChildDocumentsHeadingCount($amountOfCurrentHeading)"/>
+                </xsl:call-template>
+            </xsl:when>
+        </xsl:choose>
+
+    </xsl:template>
+
+
+    <xsl:template name="addHeadingInfo">
+        <xsl:param name="fileURL"/>
+        <xsl:param name="previousHeader"/>
+        <xsl:param name="nodeToSearchForHeading"/>
+
+        <xsl:variable name="previousHeader2" select="number($previousHeader)"/>
+        <xsl:for-each select="$nodeToSearchForHeading">
+
+            <xsl:variable name="title" select="normalize-space(.)"/>
+
+            <xsl:variable name="encodedTitle">
+                <xsl:call-template name="encode-string">
+                    <!-- the space has to be normalized,
+                        otherwise an illegal argument exception will be thrown for XT-->
+                     <xsl:with-param name="textToBeEncoded" select="$title"/>
+                </xsl:call-template>
+            </xsl:variable>
+
+            <xsl:element name="heading">
+                <!-- odd but 'descendant:text()' didn't work, but '.', to get all text nodes of the header -->
+                <xsl:attribute name="title"><xsl:value-of select="$title"/></xsl:attribute>
+                <xsl:attribute name="encoded-title"><xsl:value-of select="$encodedTitle"/></xsl:attribute>
+                <xsl:attribute name="level"><xsl:value-of select="@text:level"/></xsl:attribute>
+                <xsl:attribute name="file-url"><xsl:value-of select="$fileURL"/></xsl:attribute>
+                <xsl:attribute name="header-no"><xsl:value-of select="position() + $previousHeader2"/></xsl:attribute>
+            </xsl:element>
+        </xsl:for-each>
+
+    </xsl:template>
+
+
+    <xsl:template name="getAllHeaderNo">
+
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:get-all-child-documents-heading-count')">
+                <xsl:call-template name="addAllHeaderNoElement">
+                    <xsl:with-param name="allHeader"   select="sxghelper:get-all-child-documents-heading-count()"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.getAllChildDocumentsHeadingCount')">
+                <xsl:call-template name="addAllHeaderNoElement">
+                    <xsl:with-param name="allHeader"   select="java:com.sun.star.xslt.helper.SxgChildTransformer.getAllChildDocumentsHeadingCount()"/>
+                </xsl:call-template>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+    <xsl:template name="addAllHeaderNoElement">
+        <xsl:param name="allHeader"/>
+
+        <xsl:element name="heading-count">
+            <xsl:value-of select="$allHeader"/>
+        </xsl:element>
+
+    </xsl:template>
+
+
+    <!-- ******************************************************************************************************* -->
+    <!-- ***  Creation of a line of links at the beginning and end of a child document to enhance usability  *** -->
+    <!-- ******************************************************************************************************* -->
+
+    <xsl:template name="add-child-document-usability-links">
+        <xsl:element name="center">
+            <xsl:element name="small">
+                <xsl:text>[ </xsl:text>
+
+
+                <xsl:variable name="globalDocumentDir" select="sxghelper:get-global-document-dir()"/>
+                <xsl:variable name="currentChildNo" select="number($contentTableHeadings/heading[$globalDocumentRefToCurrentFile = @file-url]/@child-document-no)"/>
+                <xsl:variable name="earlierDocURL" select="$contentTableHeadings/heading[($currentChildNo - 1) = @child-document-no]/@out-file-url"/>
+<!--
+<xsl:message>from: <xsl:value-of select="$globalDocumentRefToCurrentFile"/></xsl:message>
+<xsl:message>to: <xsl:value-of select="$earlierDocURL"/></xsl:message>
+<xsl:message>Is: <xsl:call-template name="get-relative-file-ref">
+                    <xsl:with-param name="sourceFileRef" select="$globalDocumentRefToCurrentFile"/>
+                    <xsl:with-param name="targetFileRef" select="$earlierDocURL"/>
+                 </xsl:call-template>
+</xsl:message>-->
+
+
+                <xsl:if test="$earlierDocURL">
+                    <xsl:element name="a">
+                        <xsl:attribute name="href">
+                            <!-- when the links starts with a '#' it's a link to the Content Table-->
+                            <xsl:choose>
+                                <xsl:when test="starts-with($earlierDocURL, '#')">
+
+                                    <xsl:call-template name="get-relative-file-ref">
+                                        <xsl:with-param name="sourceFileRef" select="$globalDocumentRefToCurrentFile"/>
+                                        <xsl:with-param name="targetFileRef" select="."/>
+                                    </xsl:call-template>
+<!--        <xsl:value-of select="concat($contentTableURL, $earlierDocURL)"/>-->
+                                </xsl:when>
+                                <xsl:otherwise>
+
+                                    <xsl:call-template name="get-relative-file-ref">
+                                        <xsl:with-param name="sourceFileRef" select="$globalDocumentRefToCurrentFile"/>
+                                        <xsl:with-param name="targetFileRef" select="$earlierDocURL"/>
+                                    </xsl:call-template>
+<!--
+
+                                    <xsl:value-of select="concat($globalDocumentDir, $earlierDocURL)"/>-->
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:attribute>
+                        <xsl:text>Previous document</xsl:text>
+                    </xsl:element>
+
+                    <xsl:text> | </xsl:text>
+                </xsl:if>
+
+                <xsl:element name="a">
+                    <xsl:attribute name="href">
+                        <!-- when globalDocumentRefToCurrentFile is unset the current file is the Content Table-->
+                        <xsl:choose>
+                            <xsl:when test="$globalDocumentRefToCurrentFile">
+                                <xsl:variable name="contentTableDir">
+                                    <xsl:call-template name="get-name-of-table-of-content-document"/>
+                                </xsl:variable>
+
+                                <xsl:call-template name="get-relative-file-ref">
+                                    <xsl:with-param name="sourceFileRef" select="$globalDocumentRefToCurrentFile"/>
+                                    <xsl:with-param name="targetFileRef" select="$contentTableDir"/>
+                                </xsl:call-template>
+                            </xsl:when>
+                            <xsl:otherwise>
+                                <xsl:text>#</xsl:text>
+                            </xsl:otherwise>
+                        </xsl:choose>
+
+<!--                    <xsl:value-of select="$contentTableURL"/>-->
+                    </xsl:attribute>
+                    <xsl:text>Content Table</xsl:text>
+                </xsl:element>
+
+
+                <xsl:variable name="nextDocURL" select="$contentTableHeadings/heading[($currentChildNo + 1) = @child-document-no]/@out-file-url"/>
+                <xsl:if test="$nextDocURL">
+                    <xsl:text> | </xsl:text>
+                    <xsl:element name="a">
+                        <xsl:attribute name="href">
+                            <!-- when the links starts with a '#' it's a link to the Content Table-->
+                            <xsl:choose>
+                                <xsl:when test="starts-with($nextDocURL, '#')">
+                                    <xsl:call-template name="get-relative-file-ref">
+                                        <xsl:with-param name="sourceFileRef" select="$globalDocumentRefToCurrentFile"/>
+                                        <xsl:with-param name="targetFileRef" select="."/>
+                                    </xsl:call-template>
+<!--                                <xsl:value-of select="concat($contentTableURL, $nextDocURL)"/>-->
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:call-template name="get-relative-file-ref">
+                                        <xsl:with-param name="sourceFileRef" select="$globalDocumentRefToCurrentFile"/>
+                                        <xsl:with-param name="targetFileRef" select="$nextDocURL"/>
+                                    </xsl:call-template>
+<!--                                 <xsl:value-of select="concat($globalDocumentDir, $nextDocURL)"/>-->
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:attribute>
+                        <xsl:text>Next document</xsl:text>
+                    </xsl:element>
+                </xsl:if>
+                <xsl:text> ]</xsl:text>
+            </xsl:element>
+        </xsl:element>
+    </xsl:template>
+
+
+    <xsl:template name="get-relative-file-ref">
+        <xsl:param name="sourceFileRef"/>
+        <xsl:param name="targetFileRef"/>
+
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:get-relative-file-ref')">
+                <xsl:value-of select="sxghelper:get-relative-file-ref(string($sourceFileRef), string($targetFileRef))"/>
+            </xsl:when>
+            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.getRelativeFileRef')">
+                <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.getRelativeFileRef(string($sourceFileRef), string($targetFileRef))"/>
+            </xsl:when>
+        </xsl:choose>
+
+    </xsl:template>
+
+
+    <xsl:template name="get-name-of-table-of-content-document">
+
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:get-name-of-table-of-content-document')">
+                <xsl:value-of select="sxghelper:get-name-of-table-of-content-document()"/>
+            </xsl:when>
+            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.getNameOfTableOfContentDocument')">
+                <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.getNameOfTableOfContentDocument()"/>
+            </xsl:when>
+        </xsl:choose>
+
+    </xsl:template>
+
+
+       <xsl:template name="debug-content-table-headings-variable">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:message><xsl:text>**** THE HEADING VARIABLE **** </xsl:text></xsl:message>
+        <xsl:message>content-table-url: <xsl:value-of select="collectedGlobalData/content-table-headings/content-table-url"/></xsl:message>
+
+        <xsl:for-each select="$collectedGlobalData/content-table-headings/heading">
+            <xsl:message><xsl:text>**** new heading:        </xsl:text></xsl:message>
+            <xsl:message>content-table-id:      <xsl:value-of select="@content-table-id"/></xsl:message>
+            <xsl:message>child-document-no:     <xsl:value-of select="@child-document-no"/></xsl:message>
+            <xsl:message>file-url:              <xsl:value-of select="@file-url"/></xsl:message>
+            <xsl:message>out-file-url:          <xsl:value-of select="@out-file-url"/></xsl:message>
+            <xsl:message>level:                 <xsl:value-of select="@level"/></xsl:message>
+            <xsl:message>title:                 <xsl:value-of select="@title"/></xsl:message>
+            <xsl:message>encoded-title:         <xsl:value-of select="@encoded-title"/></xsl:message>
+            <xsl:message>absolute-chapter-level:<xsl:value-of select="@absolute-chapter-level"/></xsl:message>
+        </xsl:for-each>
+
+       </xsl:template>
+
+
+       <!-- To make the headings unique, the absolute heading is added to them
+            E.g. The level 1.2.3.4. would result into a 1+2+3+4 string -->
+    <xsl:template name="calc-chapter-numbers">
+        <xsl:param name="level"/>
+
+        <xsl:choose>
+            <xsl:when test="function-available('sxghelper:calc-chapter-numbers')">
+                <xsl:value-of select="sxghelper:calc-chapter-numbers($level)"/>
+            </xsl:when>
+            <xsl:when test="function-available('java:com.sun.star.xslt.helper.SxgChildTransformer.calcChapterNumbers')">
+                <xsl:value-of select="java:com.sun.star.xslt.helper.SxgChildTransformer.calcChapterNumbers($level)"/>
+            </xsl:when>
+        </xsl:choose>
+
+    </xsl:template>
+
+
+
+
+    <xsl:template match="text:p" mode="content-table">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:variable name="allTabStopStyles" select="$office:automatic-styles/style:style[@style:name = current()/@text:style-name]/style:properties/style:tab-stops"/>
+
+        <xsl:element name="table">
+            <xsl:attribute name="border">0</xsl:attribute>
+            <xsl:attribute name="class"><xsl:value-of select="@text:style-name"/></xsl:attribute>
+<!--
+<xsl:message>*********</xsl:message>
+<xsl:message>Stylename:<xsl:value-of select="@text:style-name"/></xsl:message>
+<xsl:message>position: <xsl:value-of select="count($allTabStopStyles/style:tab-stop)"/></xsl:message>
+-->
+
+            <xsl:element name="colgroup">
+                <xsl:call-template name="create-col-element">
+                    <xsl:with-param name="lastNodePosition" select="count($allTabStopStyles/style:tab-stop)"/>
+                    <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                </xsl:call-template>
+            </xsl:element>
+
+
+            <!-- all elements before the first tabStop -->
+            <xsl:variable name="testNo-RTF">
+                <xsl:apply-templates select="node()" mode="cell-content"/>
+            </xsl:variable>
+
+
+        <xsl:choose>
+            <xsl:when test="function-available('xt:node-set')">
+                <xsl:variable name="tabNodePositions" select="xt:node-set($testNo-RTF)"/>
+                       <xsl:element name="tr">
+                        <xsl:call-template name="create-td-elements">
+                    <xsl:with-param name="lastNodePosition" select="count($allTabStopStyles/style:tab-stop)"/>
+                    <xsl:with-param name="position"         select="1"/>
+                    <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                    <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:element>
+
+            </xsl:when>
+            <xsl:when test="function-available('xalan:nodeset')">
+                <xsl:variable name="tabNodePositions" select="xalan:nodeset($testNo-RTF)"/>
+                       <xsl:element name="tr">
+                        <xsl:call-template name="create-td-elements">
+                    <xsl:with-param name="lastNodePosition" select="count($allTabStopStyles/style:tab-stop)"/>
+                    <xsl:with-param name="position"         select="1"/>
+                    <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                    <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:element>
+
+            </xsl:when>
+        </xsl:choose>
+
+           <!-- <xsl:variable name="tabNodePositions" select="xt:node-set($testNo-RTF)"/>
+
+            <xsl:element name="tr">
+                <xsl:call-template name="create-td-elements">
+                    <xsl:with-param name="lastNodePosition" select="count($allTabStopStyles/style:tab-stop)"/>
+                    <xsl:with-param name="position"         select="1"/>
+                    <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                    <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:element>-->
+
+
+        </xsl:element>
+    </xsl:template>
+
+
+    <xsl:template name="create-col-element">
+        <xsl:param name="lastNodePosition"/>
+        <xsl:param name="allTabStopStyles"/>
+
+        <xsl:for-each select="$allTabStopStyles/style:tab-stop">
+            <xsl:element name="col">
+                <xsl:attribute name="style">
+                    <xsl:text>width: </xsl:text>
+                    <xsl:call-template name="grap-cell-width">
+                        <xsl:with-param name="position"         select="position()"/>
+                        <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                    </xsl:call-template>
+                </xsl:attribute>
+            </xsl:element>
+        </xsl:for-each>
+
+    </xsl:template>
+<!--
+Scenarios tabstops
+
+1) style:type of style:tab-stop is 'right' and earlier tabStop is not right
+ -> Earlier text-nodes and following text-nodes, will be put into an inner table, with two TD first aligned left, with proceding textnodes, the latter aligned right.
+
+2) style:type is 'right' and earlier tabStop is right
+ -> following text-nodes, will be put into a right aligned TD
+
+3) style:type is 'non-right' and earlier tabStop 'non-right' as well
+ -> put the preceding tab stops into a TD (left aligned is default)
+
+4) first style:type would have no right precedign tabStop
+ -> works well with first sceanrios 1 and 3
+
+5) last style:type would be a special case, if it would be left aligned, but this won't happen in our case.. :D
+
+Scenarios unmatched:
+- text:styleposition 'center' will not be matched in our case (effort for nothing), there will be only 'right' and not 'right'
+- If the last tabStop is not from text:stylepostion 'right', the length of the last cell is undefined and a document length must be found.
+  Not happens in our global document case. Also the algorithm below would have to be expanded (cp. scenario 5).
+
+-->
+    <xsl:template name="create-td-elements">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="lastNodePosition"/>
+        <xsl:param name="position"/>
+        <xsl:param name="allTabStopStyles"/>
+        <xsl:param name="tabNodePositions"/>
+<!--
+<xsl:message>++++++++</xsl:message>
+<xsl:message>Position: <xsl:value-of select="$position"/></xsl:message>
+<xsl:message>lastNodePosition: <xsl:value-of select="$lastNodePosition"/></xsl:message>
+-->
+
+        <xsl:variable name="currentStyleType" select="$allTabStopStyles/style:tab-stop[$position]/@style:type"/>
+        <xsl:variable name="earlierStyleType" select="$allTabStopStyles/style:tab-stop[$position - 1]/@style:type"/>
+        <xsl:choose>
+            <xsl:when test="$currentStyleType = 'right'">
+                <xsl:choose>
+                    <xsl:when test="$earlierStyleType = 'right'">
+                        <!--
+                        2) style:type is 'right' and earlier tabStop is right
+                            -> following text-nodes, will be put into a right aligned TD -->
+                        <xsl:element name="td">
+                            <xsl:attribute name="style">
+                                <xsl:text>align: right</xsl:text>
+                            </xsl:attribute>
+                            <xsl:call-template name="grap-cell-content-before-tab-stop">
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                <xsl:with-param name="endingTabStopPosition"  select="$position + 1"/>
+                                <xsl:with-param name="lastNodePosition" select="$lastNodePosition"/>
+                                <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+                            </xsl:call-template>
+                        </xsl:element>
+                    </xsl:when>
+                    <xsl:otherwise>
+                    <!--
+                        1) style:type of style:tab-stop is 'right' and earlier tabStop is not right
+                         -> Earlier text-nodes and following text-nodes, will be put into an inner table, with two TD first aligned left, with proceding textnodes, the latter aligned right.-->
+<!-- valid HTML but browsers make a line break (border=0 and paragraphstyle also missing):
+                        <xsl:element name="table">
+                            <xsl:element name="td">
+                                <xsl:call-template name="grap-cell-content-before-tab-stop">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                    <xsl:with-param name="endingTabStopPosition"  select="$position"/>
+                                    <xsl:with-param name="lastNodePosition" select="$lastNodePosition"/>
+                                    <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+                                </xsl:call-template>
+                            </xsl:element>
+                            <xsl:element name="td">
+                                <xsl:attribute name="style">
+                                    <xsl:text>align: right</xsl:text>
+                                </xsl:attribute>
+                                <xsl:call-template name="grap-cell-content-before-tab-stop">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                    <xsl:with-param name="endingTabStopPosition"  select="$position + 1"/>
+                                    <xsl:with-param name="lastNodePosition" select="$lastNodePosition"/>
+                                    <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+                                </xsl:call-template>
+                            </xsl:element>
+                        </xsl:element>
+-->
+                            <xsl:element name="td">
+                                <xsl:call-template name="grap-cell-content-before-tab-stop">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                    <xsl:with-param name="endingTabStopPosition"  select="$position"/>
+                                    <xsl:with-param name="lastNodePosition" select="$lastNodePosition"/>
+                                    <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+                                </xsl:call-template>
+<!-- ODK FEATURE NO PAGES
+                                <xsl:element name="td">
+                                    <xsl:attribute name="style">
+                                        <xsl:text>align: right</xsl:text>
+                                    </xsl:attribute>
+                                    <xsl:call-template name="grap-cell-content-before-tab-stop">
+                                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                        <xsl:with-param name="endingTabStopPosition"  select="$position + 1"/>
+                                        <xsl:with-param name="lastNodePosition" select="$lastNodePosition"/>
+                                        <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+                                    </xsl:call-template>
+                                </xsl:element>          -->
+                            </xsl:element>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:choose>
+                    <xsl:when test="$earlierStyleType = 'right'">
+                    </xsl:when>
+                    <xsl:otherwise>
+                    <!--
+                       3) style:type is 'non-right' and earlier tabStop 'non-right' as well
+                            -> put the preceding tab stops into a TD (left aligned is default) -->
+                        <xsl:element name="td">
+                            <xsl:call-template name="grap-cell-content-before-tab-stop">
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                <xsl:with-param name="endingTabStopPosition"  select="$position"/>
+                                <xsl:with-param name="lastNodePosition" select="$lastNodePosition"/>
+                                <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+                            </xsl:call-template>
+                        </xsl:element>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+
+        <xsl:if test="$position != $lastNodePosition">
+            <xsl:call-template name="create-td-elements">
+                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                <xsl:with-param name="lastNodePosition" select="$lastNodePosition"/>
+                <xsl:with-param name="position"         select="$position + 1"/>
+                <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                <xsl:with-param name="tabNodePositions" select="$tabNodePositions"/>
+            </xsl:call-template>
+        </xsl:if>
+    </xsl:template>
+
+
+    <xsl:template name="grap-cell-content-before-tab-stop">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="endingTabStopPosition"/>
+        <xsl:param name="tabNodePositions"/>
+        <xsl:param name="lastNodePosition"/>
+
+        <xsl:choose>
+            <xsl:when test="$endingTabStopPosition = 1">
+                <xsl:apply-templates mode="content-table" select="node()[position() &lt; $tabNodePositions/tab-stop-node-position[$endingTabStopPosition]]">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:apply-templates>
+            </xsl:when>
+            <xsl:when test="$endingTabStopPosition > $lastNodePosition">
+                <xsl:apply-templates mode="content-table" select="node()[position() > $tabNodePositions/tab-stop-node-position[$endingTabStopPosition - 1]]">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:apply-templates>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:apply-templates mode="content-table" select="node()[position() &lt; $tabNodePositions/tab-stop-node-position[$endingTabStopPosition]][position() > $tabNodePositions/tab-stop-node-position[$endingTabStopPosition - 1]]">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:apply-templates>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <xsl:template mode="content-table" match="text:s">
+        <xsl:call-template name="write-breakable-whitespace">
+            <xsl:with-param name="whitespaces" select="@text:c"/>
+        </xsl:call-template>
+    </xsl:template>
+
+
+    <xsl:template match="*" mode="cell-content">
+
+        <xsl:if test="name() = 'text:tab-stop' or *[name() = 'text:tab-stop']">
+            <xsl:element name="tab-stop-node-position">
+                <xsl:value-of select="position()"/>
+            </xsl:element>
+        </xsl:if>
+    </xsl:template>
+
+
+    <xsl:template name="grap-cell-width">
+        <xsl:param name="position"/>
+        <xsl:param name="allTabStopStyles"/>
+
+        <xsl:variable name="tabStopPosition" select="$allTabStopStyles/style:tab-stop[$position]/@style:position"/>
+        <xsl:choose>
+            <xsl:when test="contains($tabStopPosition, 'cm')">
+                <xsl:call-template name="create-cell-width">
+                    <xsl:with-param name="width"    select="number(substring-before($tabStopPosition,'cm'))"/>
+                    <xsl:with-param name="unit"     select="'cm'"/>
+                    <xsl:with-param name="position" select="$position - 1"/>
+                    <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="contains($tabStopPosition, 'in')">
+                <xsl:call-template name="create-cell-width">
+                    <xsl:with-param name="width"    select="number(substring-before($tabStopPosition,'in'))"/>
+                    <xsl:with-param name="unit"     select="'in'"/>
+                    <xsl:with-param name="position" select="$position - 1"/>
+                    <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="contains($tabStopPosition, 'ch')">
+                <xsl:call-template name="create-cell-width">
+                    <xsl:with-param name="width"    select="number(substring-before($tabStopPosition,'ch'))"/>
+                    <xsl:with-param name="unit"     select="'ch'"/>
+                    <xsl:with-param name="position" select="$position - 1"/>
+                    <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="contains($tabStopPosition, 'pt')">
+                <xsl:call-template name="create-cell-width">
+                    <xsl:with-param name="width"    select="number(substring-before($tabStopPosition,'pt'))"/>
+                    <xsl:with-param name="unit"     select="'pt'"/>
+                    <xsl:with-param name="position" select="$position - 1"/>
+                    <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                </xsl:call-template>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+    <xsl:template name="create-cell-width">
+        <xsl:param name="width"/>
+        <xsl:param name="unit"/>
+        <xsl:param name="position"/>
+        <xsl:param name="allTabStopStyles"/>
+
+        <xsl:choose>
+            <xsl:when test="$position > 1">
+                <xsl:call-template name="create-cell-width">
+                    <xsl:with-param name="width"    select="$width - number(substring-before($allTabStopStyles/style:tab-stop[$position]/@style:position,$unit))"/>
+                    <xsl:with-param name="unit"     select="$unit"/>
+                    <xsl:with-param name="position" select="$position - 1"/>
+                    <xsl:with-param name="allTabStopStyles" select="$allTabStopStyles"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="$position = 1">
+                <xsl:value-of select="concat($width - number(substring-before($allTabStopStyles/style:tab-stop[$position]/@style:position,$unit)), $unit)"/>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:value-of select="concat($width, $unit)"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+</xsl:stylesheet>
\ No newline at end of file
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/main_html.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/main_html.xsl
new file mode 100644 (file)
index 0000000..443182a
--- /dev/null
@@ -0,0 +1,462 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                xmlns:system="http://www.jclark.com/xt/java/java.lang.System"
+                xmlns:urlencoder="http://www.jclark.com/xt/java/java.net.URLEncoder"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+    <xsl:output method      ="xml"
+                encoding    ="UTF-8"
+                indent      ="yes"/>
+
+
+
+    <!--+++++ INCLUDED XSL MODULES +++++-->
+    <!-- inherited style properties will be collected and written in a CSS header (CSS) -->
+    <xsl:include href="style_header.xsl"/>
+
+    <!-- inherited style properties will be collected and written as html properties in a temporary variable (HTML4, PALM) -->
+    <xsl:include href="style_inlined.xsl"/>
+
+    <!-- our xml style properties will be mapped to CSS and HTML4.x properties -->
+    <xsl:include href="style_mapping.xsl"/>
+
+    <!-- common element handling -->
+    <xsl:include href="common.xsl"/>
+
+    <!-- table handling -->
+    <xsl:include href="table.xsl"/>
+
+    <!-- palm handling -->
+    <xsl:include href="palm.xsl"/>
+
+    <!-- global document handling -->
+    <xsl:include href="global_document.xsl"/>
+
+
+
+
+
+
+
+    <!--+++++ PARAMETER FROM THE APPLICATION AND GLOBAL VARIABLES +++++-->
+
+    <!-- MANDATORY: URL of meta stream -->
+    <xsl:param name="metaFileURL"/>
+
+    <!-- MANDATORY: URL of styles stream -->
+    <xsl:param name="stylesFileURL"/>
+
+    <!-- MANDATORY: for resolving relative links
+        For resolving realtive links to the packed SO document, i.e. the path/URL of the jared sxw file (e.g. meta.xml, styles.xml, links to graphics in a relative directory) -->
+    <xsl:param name="absoluteSourceDirRef"/>
+
+    <!-- OPTIONAL (mandatory, when when source is compressed): Necessary for the in the packed OO document embedded files (mostly graphics from the compressed /Picture dir).
+         When the OpenOffice (OO) file has been unpacked the absoluteSoureDirRef can be taken,
+         Otherwise, a JAR URL could be choosen or when working with OpenOffice a so called Package-URL encoded over HTTP can be used to
+         access the jared contents of the the jared document. . -->
+    <xsl:param name="jaredRootURL" select="$absoluteSourceDirRef"/>
+
+    <!-- OPTIONAL (mandatory, when used in session based environment)
+         Useful for WebApplications: if a HTTP session is not cookie based, URL rewriting is beeing used (the session is appended to the URL).
+         This URL session is used when creating links to graphics by XSLT. Otherwise the user havt to log again in for every graphic he would like to see. -->
+    <xsl:param name="optionalURLSuffix"/>
+
+    <!-- OPTIONAL: DPI (dots per inch) the standard solution of given pictures (necessary for the conversion of 'cm' into 'pixel')-->
+    <!-- Although many pictures have the 96 dpi resolution, a higher resoltion give better results for common browsers -->
+    <xsl:param name="dpi" select="96"/>
+
+    <!-- OPTIONAL: in case of using a different processor than a JAVA XSLT, you can unable the Java functionality
+         (i.e. debugging time and encoding chapter names for the content-table as href and anchors ) -->
+    <xsl:param name="disableJava"    select="false"/>
+    <xsl:param name="isJavaDisabled" select="boolean($disableJava)"/>
+
+    <!-- OPTIONAL: user-agent will be differntiated by this parameter given by application (e.g. java servlet)-->
+    <xsl:param name="outputType" select="'CSS_HEADER'"/>
+    <!-- set of possible deviceTyps (WML is set in its own startfile main_wml.xsl):
+    <xsl:param name="outputType" select="'CSS_HEADER'"/>
+    <xsl:param name="outputType" select="'CSS_INLINED'"/>
+    <xsl:param name="outputType" select="'PALM'"/> -->
+
+    <!-- OPTIONAL: for activating the debug mode set the variable here to 'true()' or give any value from outside -->
+    <xsl:param name="debug"         select="false"/>
+    <xsl:param name="isDebugMode"   select="boolean($debug)"/>
+
+<!-- *************************************************************************
+    OPTIONAL: NEEDED IN CONNECTION WITH A GLOBAL DOCUMENT -->
+
+    <!--SUMMARY:
+         following parameter triggers a (quite time consuming) enabling of bookmarks in the table-of-content.
+        IN DETAIL:
+         Currently some links used in the Office XML (e.g. in the content table as '#7.Some%20Example%20Headline%7Outline')
+         is not a valid URL (cmp. bug id# 102311). No file destination is specified nor exist any anchor element for these
+         links in the Office XML.
+         A workaround for this transformation therefore had to be made. This time-consuming mechanism is disabled by default and
+         can be activated by a parameter (i.e. 'disableLinkedTableOfContent'). A creation of an anchor is made for each header element.
+         All header titles gonna be encoding to be usable in a relative URL.    -->
+    <xsl:param name="disableLinkedTableOfContent" select="false()"/>
+
+    <!-- The chapter numbers of the current document (as a sequence of a global document) is dependent of the number
+        of chapter of the same level in preceding documents. -->
+    <xsl:param name="precedingChapterLevel1"  select="0"/>
+    <xsl:param name="precedingChapterLevel2"  select="0"/>
+    <xsl:param name="precedingChapterLevel3"  select="0"/>
+    <xsl:param name="precedingChapterLevel4"  select="0"/>
+    <xsl:param name="precedingChapterLevel5"  select="0"/>
+    <xsl:param name="precedingChapterLevel6"  select="0"/>
+    <xsl:param name="precedingChapterLevel7"  select="0"/>
+    <xsl:param name="precedingChapterLevel8"  select="0"/>
+    <xsl:param name="precedingChapterLevel9"  select="0"/>
+    <xsl:param name="precedingChapterLevel10" select="0"/>
+
+    <!-- XML documents containing a table of contents,
+        gonna link for usability reason above each chapter to the preceding and following document and the content table -->
+    <xsl:param name="contentTableURL"/>
+
+    <!-- Needed for the bug workaround of missing content table links
+        by this ambigous HTML references from the content table can be evoided-->
+    <xsl:param name="globalDocumentRefToCurrentFile"/>
+
+    <!-- Needed for the bug workaround of missing content table links
+        by this node-set the relation between content-table link and children document header can be unambigous established -->
+    <xsl:param name="contentTableHeadings"/>
+
+
+<!-- END OF GLOBAL DOCUMENT SECTION
+*************************************************************************-->
+
+
+
+    <!-- works for normal separated zipped xml files as for flat filter single xml file format as well -->
+    <xsl:variable name="office:meta-file"           select="document($metaFileURL)"/>
+    <xsl:variable name="office:styles-file"         select="document($stylesFileURL)"/>
+    <xsl:variable name="office:font-decls"          select="$office:styles-file/*/office:font-decls"/>
+    <xsl:variable name="office:styles"              select="$office:styles-file/*/office:styles"/>
+    <!-- office:automatic-styles may occure in two different files (i.d. content.xml and styles.xml). Furthermore the top level tag is different in a flat xml file -->
+    <xsl:variable name="office:automatic-styles"    select="/*/office:automatic-styles"/>
+
+    <!-- simple declaration of WML used to avoid parser errors -->
+    <xsl:variable name="wap-paragraph-elements-without-table-row"/>
+    <xsl:variable name="wap-paragraph-elements"/>
+    <xsl:template name="wml-repeat-write-row"/>
+
+
+    <!-- ************************************* -->
+    <!-- *** build the propriate HTML file *** -->
+    <!-- ************************************* -->
+
+    <xsl:template match="/">
+
+        <!--<xsl:message>
+
+
+        Entered the styleSheets, transformation begins... </xsl:message>-->
+
+        <xsl:choose>
+            <xsl:when test="$isDebugMode">
+                <xsl:call-template name="check-parameter"/>
+
+                <xsl:if test="not($isJavaDisabled)">
+                    <xsl:call-template name="debug-style-collecting-time"/>
+                </xsl:if>
+            </xsl:when>
+            <xsl:otherwise>
+                <!-- to access the variable like a node-set it is necessary to convert it
+                     from a result-tree-fragment (RTF) to a node set using the James Clark extension -->
+                <xsl:variable name="collectedGlobalData-RTF">
+                    <xsl:call-template name='create-all-inline-styles'/>
+                </xsl:variable>
+
+                <xsl:choose>
+                    <xsl:when test="function-available('xt:node-set')">
+                        <xsl:call-template name="start">
+                            <xsl:with-param name="collectedGlobalData" select="xt:node-set($collectedGlobalData-RTF)"/>
+                        </xsl:call-template>
+                    </xsl:when>
+                    <xsl:when test="function-available('xalan:nodeset')">
+                        <xsl:call-template name="start">
+                            <xsl:with-param name="collectedGlobalData" select="xalan:nodeset($collectedGlobalData-RTF)"/>
+                        </xsl:call-template>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:element name="NodeSetFunctionNotAvailable"/>
+                        <xsl:call-template name="start"/>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <xsl:template name="start">
+        <xsl:param name="collectedGlobalData"/>
+
+        <xsl:choose>
+            <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+            <xsl:when test="$outputType = 'CSS_HEADER'">
+                <xsl:element name="html">
+                    <xsl:element name="head">
+                        <xsl:if test="$isDebugMode"><xsl:message>CSS helper variable will be created....</xsl:message></xsl:if>
+                        <xsl:call-template name='common-header-properties'/>
+                        <xsl:if test="$isDebugMode"><xsl:message>CSS variable ready, header will be created....</xsl:message></xsl:if>
+                        <!-- constructing the css header simulating inheritance of style-families by style order -->
+                        <xsl:call-template name='create-css-styleheader'/>
+                        <xsl:if test="$isDebugMode"><xsl:message>CSS header creation finished!</xsl:message></xsl:if>
+                    </xsl:element>
+
+
+
+                    <xsl:variable name="backgroundImageURL" select="$office:automatic-styles/style:page-master/style:properties/style:background-image/@xlink:href"/>
+                    <xsl:element name="body">
+                        <!-- background image -->
+                        <xsl:if test="$backgroundImageURL">
+                            <xsl:attribute name="background">
+                                <xsl:choose>
+                                    <!-- for images jared in open office document -->
+                                    <xsl:when test="contains($backgroundImageURL, '#Pictures/')">
+                                        <!-- creating an absolute http URL to the contained/packed image file -->
+                                        <xsl:value-of select="concat($jaredRootURL, '/Pictures/', substring-after($backgroundImageURL, '#Pictures/'), $optionalURLSuffix)"/>
+                                    </xsl:when>
+                                    <xsl:otherwise>
+                                        <xsl:attribute name="src"><xsl:value-of select="$backgroundImageURL"/></xsl:attribute>
+                                    </xsl:otherwise>
+                                </xsl:choose>
+                            </xsl:attribute>
+                        </xsl:if>
+
+                        <!-- processing the content of the xml file -->
+                        <xsl:apply-templates select="/*/office:body">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:apply-templates>
+                    </xsl:element>
+
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ HTML 4.0 INLINING  +++++-->
+            <xsl:when test="$outputType = 'CSS_INLINED'">
+                <xsl:element name="html">
+                    <xsl:element name="head">
+                        <xsl:call-template name='common-header-properties'/>
+                    </xsl:element>
+
+                    <xsl:variable name="backgroundImageURL" select="$office:automatic-styles/style:page-master/style:properties/style:background-image/@xlink:href"/>
+                    <xsl:element name="body">
+                        <!-- background image -->
+                        <xsl:if test="$backgroundImageURL">
+                            <xsl:attribute name="background">
+                                <xsl:choose>
+                                    <!-- for images jared in open office document -->
+                                    <xsl:when test="contains($backgroundImageURL, '#Pictures/')">
+                                        <!-- creating an absolute http URL to the contained/packed image file -->
+                                        <xsl:value-of select="concat($jaredRootURL, '/Pictures/', substring-after($backgroundImageURL, '#Pictures/'), $optionalURLSuffix)"/>
+                                    </xsl:when>
+                                    <xsl:otherwise>
+                                        <xsl:attribute name="src"><xsl:value-of select="$backgroundImageURL"/></xsl:attribute>
+                                    </xsl:otherwise>
+                                </xsl:choose>
+                            </xsl:attribute>
+                        </xsl:if>
+                        <xsl:apply-templates select="/*/office:body">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:apply-templates>
+                    </xsl:element>
+                </xsl:element>
+            </xsl:when>
+
+            <!--+++++ PALM-VII (3.2 HTML SUBSET)  +++++-->
+            <xsl:when test="$outputType = 'PALM'">
+                <!-- the proxy will convert the html file later to PQA -->
+                <xsl:element name="html">
+                    <xsl:element name="head">
+                        <xsl:call-template name='palm-header-properties'/>
+                    </xsl:element>
+
+                    <xsl:element name="body">
+                        <!-- processing the content of the xml file -->
+                        <xsl:apply-templates select="/*/office:body">
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:apply-templates>
+                    </xsl:element>
+                </xsl:element>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <!-- ********************************************* -->
+    <!-- *** Header for CSS_INLINED and CSS_HEADER *** -->
+    <!-- ********************************************* -->
+
+    <xsl:template name='common-header-properties'>
+        <xsl:apply-templates select="$office:meta-file/*/office:meta/dc:title"/>
+        <xsl:apply-templates select="$office:meta-file/*/office:meta/dc:description"/>
+<!--2DO add further header elements..
+        <xsl:apply-templates select="$office:meta-file/*/office:meta/dc:subject"/>
+        <xsl:apply-templates select="$office:meta-file/*/office:meta/meta:keywords[postition()=1]"/>-->
+    </xsl:template>
+
+    <xsl:template match="dc:title">
+        <xsl:element name="title">
+            <xsl:value-of select="."/>
+        </xsl:element>
+    </xsl:template>
+
+    <xsl:template match="dc:description">
+        <xsl:element name="meta">
+            <xsl:attribute name="name">
+                <xsl:text>description</xsl:text>
+            </xsl:attribute>
+            <xsl:attribute name="content">
+                <xsl:value-of select="."/>
+            </xsl:attribute>
+        </xsl:element>
+    </xsl:template>
+
+
+    <!-- ********************************************* -->
+    <!-- *** Measuring the time for style creating *** -->
+    <!-- ********************************************* -->
+
+
+    <xsl:template name="debug-style-collecting-time">
+
+        <xsl:variable name="startTime-RTF">
+            <xsl:choose>
+                <xsl:when test="function-available('system:current-time-millis')">
+                    <xsl:value-of select="system:current-time-millis()"/>
+                </xsl:when>
+                <xsl:when test="function-available('java:java.lang.System.currentTimeMillis')">
+                    <xsl:value-of select="java:java.lang.System.currentTimeMillis()"/>
+                </xsl:when>
+            </xsl:choose>
+        </xsl:variable>
+
+
+
+        <xsl:variable name="collectedGlobalData-RTF">
+            <xsl:call-template name='create-all-inline-styles'/>
+        </xsl:variable>
+
+
+        <xsl:choose>
+            <xsl:when test="function-available('xt:node-set')">
+                <xsl:message>Creating the inline styles....</xsl:message>
+                <xsl:variable name="startTime"              select="number(xt:node-set($startTime-RTF))"/>
+                <xsl:variable name="collectedGlobalData"    select="xt:node-set($collectedGlobalData-RTF)"/>
+                <xsl:variable name="endTime"                select="system:current-time-millis()"/>
+
+                <xsl:message>Time for instantiating style variable: <xsl:value-of select="($endTime - $startTime)"/> ms</xsl:message>
+                <xsl:call-template name="start">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="function-available('xalan:nodeset')">
+                <xsl:message>Creating the inline styles....</xsl:message>
+                <xsl:variable name="startTime"              select="number(xalan:nodeset($startTime-RTF))"/>
+                <xsl:variable name="endTime"                select="java:java.lang.System.currentTimeMillis()"/>
+                <xsl:variable name="collectedGlobalData"    select="xalan:nodeset($collectedGlobalData-RTF)"/>
+
+                <xsl:message>Time for instantiating style variable: <xsl:value-of select="($endTime - $startTime)"/> ms</xsl:message>
+                <xsl:call-template name="start">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:when>
+        </xsl:choose>
+
+    </xsl:template>
+
+    <!-- DEBUG purpose only: checking the parameters of this template-->
+    <xsl:template name="check-parameter">
+        <xsl:message>Parameter dpi: <xsl:value-of select="$dpi"/></xsl:message>
+        <xsl:message>Parameter metaFileURL: <xsl:value-of select="$metaFileURL"/></xsl:message>
+        <xsl:message>Parameter stylesFileURL: <xsl:value-of select="$stylesFileURL"/></xsl:message>
+        <xsl:message>Parameter absoluteSourceDirRef: <xsl:value-of select="$absoluteSourceDirRef"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel1 : <xsl:value-of select="$precedingChapterLevel1"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel2 : <xsl:value-of select="$precedingChapterLevel2"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel3 : <xsl:value-of select="$precedingChapterLevel3"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel4 : <xsl:value-of select="$precedingChapterLevel4"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel5 : <xsl:value-of select="$precedingChapterLevel5"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel6 : <xsl:value-of select="$precedingChapterLevel6"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel7 : <xsl:value-of select="$precedingChapterLevel7"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel8 : <xsl:value-of select="$precedingChapterLevel8"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel9 : <xsl:value-of select="$precedingChapterLevel9"/></xsl:message>
+        <xsl:message>Parameter precedingChapterLevel10: <xsl:value-of select="$precedingChapterLevel10"/></xsl:message>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/palm.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/palm.xsl
new file mode 100644 (file)
index 0000000..212edb1
--- /dev/null
@@ -0,0 +1,404 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                xmlns:system="http://www.jclark.com/xt/java/java.lang.System"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+    <xsl:output cdata-section-elements="meta"/>
+
+
+    <!-- **************************** -->
+    <!-- *** specific palm header *** -->
+    <!-- **************************** -->
+
+    <xsl:template name='palm-header-properties'>
+        <xsl:element name="meta">
+            <xsl:attribute name="name">PalmComputingPlatform</xsl:attribute>
+            <xsl:attribute name="content">true</xsl:attribute>
+        </xsl:element>
+        <xsl:element name="meta">
+            <xsl:attribute name="name">HandheldFriendly</xsl:attribute>
+            <xsl:attribute name="content">true</xsl:attribute>
+        </xsl:element>
+        <xsl:element name="meta">
+            <xsl:attribute name="name">HistoryListText</xsl:attribute>
+            <xsl:attribute name="content">Dateimanager&#10;: &amp;date &amp;time</xsl:attribute>
+        </xsl:element>
+        <xsl:element name="meta">
+            <xsl:attribute name="name">description</xsl:attribute>
+            <xsl:attribute name="content">StarPortal</xsl:attribute>
+        </xsl:element>
+        <xsl:element name="meta">
+            <xsl:attribute name="name">keywords</xsl:attribute>
+            <xsl:attribute name="content">starportal, staroffice, software</xsl:attribute>
+        </xsl:element>
+        <xsl:element name="meta">
+            <xsl:attribute name="http-equiv">Content-Type</xsl:attribute>
+            <xsl:attribute name="content">text/html; charset=iso-8859-1</xsl:attribute>
+        </xsl:element>
+    </xsl:template>
+
+
+    <!-- ********************************* -->
+    <!-- *** creating table attributes *** -->
+    <!-- ********************************* -->
+
+    <!-- table data (td) and table header (th) attributes -->
+    <xsl:template name="create-attribute-ALIGN">
+        <xsl:param name="styleProperties"/>
+
+        <xsl:if test="contains($styleProperties, 'align')">
+            <xsl:attribute name="align">
+                 <xsl:choose>
+                    <xsl:when test="contains($styleProperties, 'align:left')">
+                        <xsl:text>left</xsl:text>
+                    </xsl:when>
+                    <xsl:when test="contains($styleProperties, 'align:right')">
+                        <xsl:text>right</xsl:text>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:text>center</xsl:text>
+                    </xsl:otherwise>
+                 </xsl:choose>
+            </xsl:attribute>
+        </xsl:if>
+    </xsl:template>
+
+
+    <!-- ********************************* -->
+    <!-- *** creating List attributes  *** -->
+    <!-- ********************************* -->
+<!--
+    <xsl:template name="create-list-attributes">
+        <xsl:param name="styleProperties"/>
+
+
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+2 be implemented
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+
+    </xsl:template>
+-->
+
+    <!-- ************************************************ -->
+    <!-- *** creating nested format tags (PALM & WML) *** -->
+    <!-- ************************************************ -->
+
+    <!-- Italic -->
+    <xsl:template name="create-nested-format-tags">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="styleProperties"/>
+        <xsl:choose>
+            <xsl:when test="contains($styleProperties, 'italic')">
+                <xsl:element name="i">
+                    <xsl:call-template name="bold">
+                        <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:call-template name="bold">
+                    <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- Bold -->
+    <xsl:template name="bold">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="styleProperties"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($styleProperties, 'bold')">
+                <xsl:element name="b">
+                    <xsl:call-template name="underline">
+                        <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:call-template name="underline">
+                    <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- Underline : last format attribute, which is also used from WML - WML ends here! -->
+    <xsl:template name="underline">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="styleProperties"/>
+
+        <xsl:choose>
+            <xsl:when test="$outputType = 'PALM'">
+                <xsl:choose>
+                    <xsl:when test="contains($styleProperties, 'underline')">
+                        <xsl:element name="u">
+                            <xsl:call-template name="strikethrough">
+                                <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:call-template>
+                        </xsl:element>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:call-template name="strikethrough">
+                            <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:call-template>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:choose>
+                    <xsl:when test="contains($styleProperties, 'underline')">
+                        <xsl:element name="u">
+                            <xsl:apply-templates>
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:apply-templates>
+                        </xsl:element>
+                    </xsl:when>
+                    <xsl:otherwise>
+                        <xsl:apply-templates>
+                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        </xsl:apply-templates>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- strikethrough -->
+    <xsl:template name="strikethrough">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="styleProperties"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($styleProperties, 'strike')">
+                <xsl:element name="strike">
+                    <xsl:call-template name="align">
+                        <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:call-template name="align">
+                    <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- Alignment -->
+    <xsl:template name="align">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="styleProperties"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($styleProperties, 'align')">
+                <xsl:element name="div">
+                    <xsl:attribute name="align">
+                         <xsl:choose>
+                            <xsl:when test="contains($styleProperties, 'align:left')">
+                                <xsl:text>left</xsl:text>
+                            </xsl:when>
+                            <xsl:when test="contains($styleProperties, 'align:right')">
+                                <xsl:text>right</xsl:text>
+                            </xsl:when>
+                            <xsl:otherwise>
+                                <xsl:text>center</xsl:text>
+                            </xsl:otherwise>
+                         </xsl:choose>
+                    </xsl:attribute>
+                    <xsl:call-template name="font_combined">
+                        <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:call-template>
+                </xsl:element>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:call-template name="font_combined">
+                    <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- Both size and Color for font -->
+    <xsl:template name="font_combined">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="styleProperties"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($styleProperties, 'color') and contains($styleProperties, 'size')">
+                <xsl:element name="font">
+
+                    <xsl:attribute name="color">
+                         <xsl:choose>
+                            <xsl:when test="contains($styleProperties, 'color:#000000')">
+                                <xsl:text>#000000</xsl:text>
+                            </xsl:when>
+                            <xsl:otherwise>
+                                <xsl:text>#FFFFFF</xsl:text>
+                            </xsl:otherwise>
+                         </xsl:choose>
+                    </xsl:attribute>
+
+                    <xsl:attribute name="size">
+                        <xsl:value-of select="substring-after(substring-before($styleProperties ,':size'), 'size:')"/>
+                    </xsl:attribute>
+
+                    <!-- get the embedded content -->
+                    <xsl:apply-templates>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:apply-templates>
+                </xsl:element>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:call-template name="font_simple">
+                    <xsl:with-param name="styleProperties" select="$styleProperties"/>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- size or Color for font -->
+    <xsl:template name="font_simple">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="styleProperties"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($styleProperties, 'color')">
+                <xsl:element name="font">
+                    <xsl:attribute name="color">
+                         <xsl:choose>
+                            <xsl:when test="contains($styleProperties, 'color:#000000')">
+                                <xsl:text>#000000</xsl:text>
+                            </xsl:when>
+                            <xsl:otherwise>
+                                <xsl:text>#FFFFFF</xsl:text>
+                            </xsl:otherwise>
+                         </xsl:choose>
+                    </xsl:attribute>
+
+                    <!-- get the embedded content -->
+                    <xsl:apply-templates>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:apply-templates>
+                </xsl:element>
+            </xsl:when>
+
+            <xsl:when test="contains($styleProperties, 'size')">
+                <xsl:element name="font">
+                    <xsl:attribute name="size">
+                        <xsl:value-of select="substring-after(substring-before($styleProperties ,':size'), 'size:')"/>
+                    </xsl:attribute>
+
+                    <!-- get the embedded content -->
+                    <xsl:apply-templates>
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    </xsl:apply-templates>
+                </xsl:element>
+            </xsl:when>
+
+            <xsl:otherwise>
+                <!-- get the embedded content -->
+                <xsl:apply-templates>
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                </xsl:apply-templates>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/style_header.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/style_header.xsl
new file mode 100644 (file)
index 0000000..eeb0c20
--- /dev/null
@@ -0,0 +1,379 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                xmlns:system="http://www.jclark.com/xt/java/java.lang.System"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+
+
+    <!-- ****************************** -->
+    <!-- *** style sheet processing *** -->
+    <!-- ****************************** -->
+
+
+    <xsl:template name='create-css-styleheader'>
+        <xsl:comment>
+            <xsl:text>The CSS style header method for setting styles</xsl:text>
+        </xsl:comment>
+        <xsl:element name="style">
+            <xsl:attribute name="type">text/css</xsl:attribute>
+            <xsl:comment>
+                <xsl:text>
+
+        </xsl:text>
+                <xsl:call-template name="write-default-styles"/>
+
+                <!-- THE STYLE PROPERTIES OF THE FIRST WRITTEN STYLE (PARENT) IS GIVEN OUT -->
+
+                <!-- 1) styles from office:styles are possible parent from all (itself or office:automatic-styles).
+                     Therefore they are created first.
+                     Beginning with the top-level parents (the styles without any parent). -->
+                <xsl:for-each select="$office:styles/style:style[not(@style:parent-style-name)]">
+
+                    <xsl:call-template name="write-styleproperty-line"/>
+                    <xsl:call-template name="write-styleproperty-lines-for-children"/>
+                </xsl:for-each>
+
+                <xsl:text> </xsl:text>
+
+                <!-- 2) styles from office:automatic-styles can only be parent of styles from the office:automatic-styles section.
+                     Beginning with top-level styles, again, all children style will be recursivly traversed -->
+                <xsl:for-each select="$office:automatic-styles/style:style[not(@style:parent-style-name)]">
+                    <xsl:call-template name="write-styleproperty-line">
+                        <xsl:with-param name="searchOnlyInAutomaticStyles" select="true()"/>
+                    </xsl:call-template>
+                    <xsl:call-template name="write-styleproperty-lines-for-children">
+                        <xsl:with-param name="searchOnlyInAutomaticStyles"/>
+                    </xsl:call-template>
+                </xsl:for-each>
+            //</xsl:comment>
+        </xsl:element>
+    </xsl:template>
+
+
+    <xsl:template name='write-styleproperty-line'>
+        <xsl:param name="searchOnlyInAutomaticStyles"/>
+
+        <xsl:variable name="styleProperties">
+            <xsl:call-template name="write-style-properties">
+                <xsl:with-param name="styleAttributePath"   select="current()/style:properties/@*"/>
+            </xsl:call-template>
+        </xsl:variable>
+
+        <!-- do not write styles with no css property -->
+        <xsl:if test="not(string-length($styleProperties) = 0)">
+            <!-- write out the name of the current (parent) style in the CSS headersection (e.g. "span.myStyle") -->
+            <xsl:call-template name="write-style-name">
+                <xsl:with-param name="is-parent-style" select="true()"/>
+            </xsl:call-template>
+
+            <!-- the names of all styles children will be written out(office:style AND office:automatic-style) -->
+            <xsl:call-template name="write-children-style-names">
+                <xsl:with-param name="parentStyleName"          select="@style:name"/>
+                <xsl:with-param name="parentStyleFamily"        select="@style:family"/>
+                <xsl:with-param name="searchOnlyInAutomaticStyles"/>
+            </xsl:call-template>
+
+        <!-- the style properties of the first written style (parent) is given out -->
+        <xsl:text> {
+                </xsl:text>
+                <xsl:value-of select="$styleProperties"/>
+        <xsl:text>}
+        </xsl:text>
+
+        </xsl:if>
+
+
+
+    </xsl:template>
+
+
+
+
+    <!-- RECURSION WITH ENDCONDITON: adding style classes for all existing childs -->
+    <xsl:template name='write-styleproperty-lines-for-children'>
+        <xsl:param name="searchOnlyInAutomaticStyles"/>
+
+        <xsl:variable name="parentStyleName"    select="@style:name"/>
+        <xsl:variable name="parentStyleFamily"  select="@style:family"/>
+
+        <xsl:if test="not(searchOnlyInAutomaticStyles)">
+            <xsl:for-each select="../style:style[@style:family=$parentStyleFamily and @style:parent-style-name=$parentStyleName]">
+                <xsl:call-template name="write-styleproperty-line"/>
+                <xsl:call-template name="write-styleproperty-lines-for-children"/>
+            </xsl:for-each>
+        </xsl:if>
+        <xsl:for-each select="$office:automatic-styles/style:style[@style:family=$parentStyleFamily and @style:parent-style-name=$parentStyleName]">
+            <xsl:call-template name="write-styleproperty-line">
+                <xsl:with-param name="searchOnlyInAutomaticStyles"/>
+            </xsl:call-template>
+            <xsl:call-template name="write-styleproperty-lines-for-children">
+                <xsl:with-param name="searchOnlyInAutomaticStyles"/>
+            </xsl:call-template>
+        </xsl:for-each>
+    </xsl:template>
+
+
+    <xsl:template name="write-default-styles">
+
+        <!-- some default attributes in xml have to be explicitly set in HTML (e.g. margin-top="0") -->
+        <xsl:text>*.OOo_defaults</xsl:text>
+
+                <xsl:for-each select="$office:styles/style:style">
+                    <xsl:text>, </xsl:text>
+                    <xsl:value-of select="concat('*.', translate(@style:name, '. %()/\', ''))"/>
+                </xsl:for-each>
+
+                <xsl:for-each select="$office:automatic-styles/style:style">
+                    <xsl:text>, </xsl:text>
+                    <xsl:value-of select="concat('*.', translate(@style:name, '. %()/\', ''))"/>
+                </xsl:for-each>
+        <!-- 2DO: the defaults might be better collected and written in a separated (XML) file -->
+<xsl:text> {
+                margin-top:0cm; margin-bottom:0cm; }
+        </xsl:text>
+
+        <xsl:for-each select="$office:styles/style:default-style">
+            <xsl:call-template name="write-default-style"/>
+        </xsl:for-each>
+
+        <xsl:for-each select="$office:automatic-styles/style:default-style">
+            <xsl:call-template name="write-default-style"/>
+        </xsl:for-each>
+
+    </xsl:template>
+
+
+
+    <xsl:template name="write-default-style">
+        <xsl:variable name="family-style" select="@style:family"/>
+
+        <!-- some default attributes for format families (e.g. graphics, paragraphs, etc.) written as style:default-style -->
+        <xsl:value-of select="concat('*.', translate($family-style, '. %()/\', ''), '_defaults')"/>
+
+        <xsl:for-each select="$office:styles/style:style[@style:family = $family-style]">
+            <xsl:text>, </xsl:text>
+            <xsl:value-of select="concat('*.', translate(@style:name, '. %()/\', ''))"/>
+        </xsl:for-each>
+
+        <xsl:for-each select="$office:automatic-styles/style:style[@style:family = $family-style]">
+            <xsl:text>, </xsl:text>
+            <xsl:value-of select="concat('*.', translate(@style:name, '. %()/\', ''))"/>
+        </xsl:for-each>
+
+
+        <xsl:variable name="styleProperties">
+            <xsl:call-template name="write-style-properties">
+                <xsl:with-param name="styleAttributePath"   select="current()/style:properties/@*"/>
+            </xsl:call-template>
+        </xsl:variable>
+
+        <!-- do not write styles with no css property -->
+        <xsl:if test="not(string-length($styleProperties) = 0)">
+        <!-- the style properties of the first written style (parent) is given out -->
+        <xsl:text> {
+                </xsl:text>
+                <xsl:value-of select="$styleProperties"/>
+        <xsl:text>}
+        </xsl:text>
+        </xsl:if>
+
+    </xsl:template>
+
+
+    <!--++
+          The parent style will be written out!
+          For each Style:family a prefix must be added
+            <!ENTITY % styleFamily
+            "(paragraph|text|section|table|table-column|table-row|table-cell|table-page|chart|graphics|default|drawing-page|presentation|control)">
+        ++-->
+    <xsl:template name="write-style-name">
+        <xsl:param name="is-parent-style"/>
+
+        <!-- This construct is for list elements. Whenever a paragraph element is being used as child of a list element the name paragraph style is been used for
+            the list item. This can be switched as the paragaph style-name and the list-style-name are in the same element.
+            Otherwise there would be formatting errors (e.g. margin-left will be used for the content in the list elment and not for the list element itself). -->
+        <xsl:variable name="style-name">
+            <xsl:choose>
+                <xsl:when test="@style:list-style-name">
+                    <xsl:value-of select="@style:list-style-name"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:value-of select="@style:name"/>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:variable>
+
+        <xsl:if test="not($is-parent-style)">
+            <xsl:text>, </xsl:text>
+        </xsl:if>
+
+        <xsl:choose>
+            <!-- normally 'p.' would be used as CSS element,
+                 but header (h1, h2,...) are also from the style:family paragraph -->
+            <xsl:when test="@style:family='paragraph'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='text'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='section'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='table'">
+                <xsl:value-of select="concat('table.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='table-column'">
+            <!-- as column styles have to be included as span styles AFTER the table (no two class attributes in TD allowed -->
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='table-row'">
+                <xsl:value-of select="concat('tr.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='table-cell'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='table-page'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='chart'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='graphics'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='default'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='drawing-page'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='presentation'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+            <xsl:when test="@style:family='control'">
+                <xsl:value-of select="concat('*.', translate($style-name, '. %()/\', ''))"/>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- finding all style child of a section and give their styleIdentifier to the output -->
+    <xsl:template name='write-children-style-names'>
+        <xsl:param name="parentStyleName" select="@style:name"/>
+        <xsl:param name="parentStyleFamily" select="@style:family"/>
+        <xsl:param name="searchOnlyInAutomaticStyles"/>
+
+
+        <!--** the names of all office:styles children will be written out
+            ** (a automatic style can only have children in the office:automatic-style section) -->
+
+        <!-- if NOT called from a office:automatic-style parent -->
+        <xsl:if test="not(searchOnlyInAutomaticStyles)">
+            <!-- for all children in the office:style section -->
+            <xsl:for-each select="../style:style[@style:family=$parentStyleFamily and @style:parent-style-name=$parentStyleName]">
+                <!-- write the style name in the css header -->
+                <xsl:call-template name="write-style-name"/>
+
+                <!-- search for child styles -->
+                <xsl:call-template name="write-children-style-names">
+                    <xsl:with-param name="parentStyleName" select="@style:name"/>
+                    <xsl:with-param name="parentStyleFamily" select="@style:family"/>
+                </xsl:call-template>
+
+            </xsl:for-each>
+        </xsl:if>
+
+        <!--** the names of all office:automatic-styles children will be written out -->
+
+        <!-- for all children in the office:automatic-style section -->
+        <xsl:for-each select="$office:automatic-styles/style:style[@style:family=$parentStyleFamily and @style:parent-style-name=$parentStyleName]">
+            <!-- write the style name in the css header -->
+            <xsl:call-template name="write-style-name"/>
+
+            <!-- search for child styles -->
+            <xsl:call-template name="write-children-style-names">
+                <xsl:with-param name="parentStyleName" select="@style:name"/>
+                <xsl:with-param name="parentStyleFamily" select="@style:family"/>
+                <xsl:with-param name="searchOnlyInAutomaticStyles"/>
+            </xsl:call-template>
+
+        </xsl:for-each>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/style_inlined.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/style_inlined.xsl
new file mode 100644 (file)
index 0000000..1915995
--- /dev/null
@@ -0,0 +1,398 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                xmlns:system="http://www.jclark.com/xt/java/java.lang.System"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+
+
+    <!-- ********************************************* -->
+    <!-- *** hard attributed (inlined) properties  *** -->
+    <!-- ********************************************* -->
+
+
+    <!-- RESTRICTIONS:
+            1)  As the styles-node-variables are NOT global, the style variables are not global, either!!
+            2)  As a list of elements can only be added to a variable as a result tree fragment the
+                extension is neccessary!!
+    -->
+
+    <!-- 2DO: Inline styles do not inherit from XML office defaults nor font:family defaults as the style header does
+              (cp. stylesheet 'style_header.xsl' and the 'write-default-styles' template) -->
+
+    <xsl:template name='create-all-inline-styles'>
+
+        <!--** traversee all style trees and their branches collecting style properties **-->
+        <xsl:element name="allstyles">
+        <!--** traversee all office:styles trees beginning with the top-level styles**-->
+            <xsl:for-each select="$office:styles/style:style[not(@style:parent-style-name)]">
+
+                <!--** give out the style properties of the parent node **-->
+                <xsl:call-template name='write-current-and-inherited-style-properties'>
+                    <xsl:with-param name="styles-node"                  select="$office:styles"/>
+                    <xsl:with-param name="style-family"                 select="@style:family"/>
+                    <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+                </xsl:call-template>
+
+                <!--** all office:styles children of the current top-level office:styles **-->
+                <xsl:call-template name='for-all-templates-child-styles'>
+                    <xsl:with-param name="parentStyleName"              select="@style:name"/>
+                    <xsl:with-param name="parentStyleFamily"            select="@style:family"/>
+                    <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+                </xsl:call-template>
+
+                <!--** all office:automatic-styles children of the current top-level style **-->
+                <xsl:call-template name='for-all-automatic-child-styles'>
+                    <xsl:with-param name="parentStyleName"              select="@style:name"/>
+                    <xsl:with-param name="parentStyleFamily"            select="@style:family"/>
+                    <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+                </xsl:call-template>
+            </xsl:for-each>
+
+        <!--** traversee all office:automatic-styles trees beginning with the top-level styles **-->
+            <xsl:for-each select="$office:automatic-styles/style:style[not(@style:parent-style-name)]">
+                <!--** give out the style properties of the parent node **-->
+                <xsl:call-template name='write-current-and-inherited-style-properties'>
+                    <xsl:with-param name="styles-node"                  select="$office:automatic-styles"/>
+                    <xsl:with-param name="style-family"                 select="@style:family"/>
+                    <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+                </xsl:call-template>
+
+                <!--** all children of the top-level office:automatic-styless  **-->
+                <xsl:call-template name='for-all-automatic-child-styles'>
+                    <xsl:with-param name="parentStyleName"              select="@style:name"/>
+                    <xsl:with-param name="parentStyleFamily"            select="@style:family"/>
+                    <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+                </xsl:call-template>
+            </xsl:for-each>
+        </xsl:element>
+    </xsl:template>
+
+
+
+    <xsl:template name='for-all-templates-child-styles'>
+        <xsl:param name="parentStyleName"/>
+        <xsl:param name="parentStyleFamily"/>
+        <xsl:param name="style-name-tokenized"/>
+
+        <xsl:for-each select="../style:style[@style:family=$parentStyleFamily and @style:parent-style-name=$parentStyleName]">
+            <!--** give out the style properties of the current node **-->
+            <xsl:element name="{$style-name-tokenized}">
+                <xsl:call-template name='write-current-and-inherited-style-properties'>
+                    <xsl:with-param name="styles-node"                  select="$office:styles"/>
+                    <xsl:with-param name="style-family"                 select="@style:family"/>
+                    <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+                </xsl:call-template>
+            </xsl:element>
+
+            <!--** for all template-children of the current office:styles  **-->
+            <xsl:call-template name='for-all-templates-child-styles'>
+                <xsl:with-param name="styles-node"                  select="$office:styles"/>
+                <xsl:with-param name="parentStyleName"              select="@style:name"/>
+                <xsl:with-param name="parentStyleFamily"            select="@style:family"/>
+                <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+            </xsl:call-template>
+
+            <!--** for all automatic-children of the current office:styles  **-->
+            <xsl:call-template name='for-all-automatic-child-styles'>
+                <xsl:with-param name="styles-node"                  select="$office:automatic-styles"/>
+                <xsl:with-param name="parentStyleName"              select="@style:name"/>
+                <xsl:with-param name="parentStyleFamily"            select="@style:family"/>
+                <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+            </xsl:call-template>
+
+        </xsl:for-each>
+    </xsl:template>
+
+
+
+    <xsl:template name='for-all-automatic-child-styles'>
+        <xsl:param name="styles-node"/>
+        <xsl:param name="parentStyleName"/>
+        <xsl:param name="parentStyleFamily"/>
+        <xsl:param name="style-name-tokenized"/>
+
+        <xsl:for-each select="$office:automatic-styles/style:style[@style:family=$parentStyleFamily and @style:parent-style-name=$parentStyleName]">
+            <!--** give out the style properties of the current node **-->
+            <xsl:element name="{$style-name-tokenized}">
+                <xsl:call-template name='write-current-and-inherited-style-properties'>
+                    <xsl:with-param name="styles-node"                  select="$office:automatic-styles"/>
+                    <xsl:with-param name="style-family"                 select="@style:family"/>
+                    <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+                </xsl:call-template>
+            </xsl:element>
+
+            <!--** for all automatic-children of the current office:automatic-styles  **-->
+            <xsl:call-template name='for-all-automatic-child-styles'>
+                <xsl:with-param name="styles-node"                  select="$office:automatic-styles"/>
+                <xsl:with-param name="parentStyleName"              select="@style:name"/>
+                <xsl:with-param name="parentStyleFamily"            select="@style:family"/>
+                <xsl:with-param name="style-name-tokenized"         select="translate(@style:name, '. %()/\', '')"/>
+            </xsl:call-template>
+        </xsl:for-each>
+    </xsl:template>
+
+
+    <xsl:template name='write-current-and-inherited-style-properties'>
+        <xsl:param name="style-family"/>
+        <xsl:param name="styles-node"/>
+        <xsl:param name="style-name-tokenized"/>
+
+        <xsl:element name="{$style-name-tokenized}">
+            <xsl:variable name="current-style-name" select="@style:name"/>
+            <xsl:variable name="parent-style-name" select="@style:parent-style-name"/>
+
+            <xsl:variable name="new-property-list">
+                <!--*** COLLECT STYLE ATTRIBUTES (only toplevel) ***-->
+                <xsl:call-template name="write-style-properties">
+                    <xsl:with-param name="styleAttributePath"   select="$styles-node/style:style[@style:family=$style-family and @style:name=$current-style-name]/style:properties/@*"/>
+                </xsl:call-template>
+            </xsl:variable>
+            <xsl:choose>
+                <!--*** @End: GIVE OUT All COLLECTED STYLE ATTRIBUTES (only toplevel) ***-->
+                <xsl:when test="string-length($parent-style-name)=0">
+                <!--** if no styleParent is given, the properties are given out at once **-->
+                    <xsl:value-of select="normalize-space($new-property-list)"/>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:variable name="new-property-names">
+                        <xsl:for-each select="$styles-node/style:style[@style:family=$style-family and @style:name=$current-style-name]/style:properties/@*">
+                            <xsl:value-of select="name()"/>
+                        </xsl:for-each>
+                    </xsl:variable>
+                    <!--** further attributes of the parent style must be collected **-->
+                    <xsl:call-template name="add-parent-style-attributes">
+                        <xsl:with-param name="property-name-list"       select="$new-property-names"/>
+                        <xsl:with-param name="complete-property-list"   select="normalize-space($new-property-list)"/>
+                        <xsl:with-param name="current-style-name"       select="$current-style-name"/>
+                        <xsl:with-param name="parent-style-name"        select="$parent-style-name"/>
+                        <xsl:with-param name="style-family"             select="$style-family"/>
+                    </xsl:call-template>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:element>
+    </xsl:template>
+
+
+
+    <xsl:template name="add-parent-style-attributes">
+        <xsl:param name="property-name-list"/>
+        <xsl:param name="complete-property-list"/>
+        <xsl:param name="current-style-name"/>
+        <xsl:param name="parent-style-name"/>
+        <xsl:param name="style-family"/>
+
+        <!--*** New two be added property names will be collected (only one variable per template) ***-->
+        <xsl:variable name="new-property-names">
+            <xsl:call-template name="get-new-style-names">
+                <xsl:with-param name="property-name-list"       select="$property-name-list"/>
+                <xsl:with-param name="parent-style-name"        select="$parent-style-name"/>
+                <xsl:with-param name="current-style-name"       select="$current-style-name"/>
+            </xsl:call-template>
+        </xsl:variable>
+
+        <xsl:choose>
+            <!--*** check if the new parent style exist in the office:automatic-styles section (defined by name and family) ***-->
+            <xsl:when test="$office:automatic-styles/style:style[@style:family='paragraph' and @style:name=$current-style-name]">
+                <!--*** RECURSION: adding new parent style attributes to the current style ***-->
+                <xsl:variable name="new-property-attributes">
+                    <xsl:call-template name="get-new-style-attributes">
+                        <xsl:with-param name="new-property-names"           select="$new-property-names"/>
+                        <xsl:with-param name="current-style-name"           select="$current-style-name"/>
+                        <xsl:with-param name="parent-style-name"            select="$parent-style-name"/>
+                    </xsl:call-template>
+                </xsl:variable>
+                <!--*** End CONDITION: the last style parent has already been executed ***-->
+                <xsl:variable name="new-parent-style-name"  select="$office:automatic-styles/style:style[@style:family='paragraph' and @style:name=$parent-style-name]/@style:parent-style-name"/>
+                <xsl:choose>
+                    <xsl:when test="string-length($new-parent-style-name)=0">
+                    <!--** no further parent is found, the given parameter property-node is the final style -->
+                        <xsl:value-of select="concat($complete-property-list,$new-property-attributes)"/>
+                    </xsl:when>
+                    <xsl:otherwise>
+                    <!--** further attributes of the parent style must be collected **-->
+                        <xsl:call-template name="add-parent-style-attributes">
+                            <xsl:with-param name="property-name-list"       select="concat($property-name-list, $new-property-names)"/>
+                            <xsl:with-param name="complete-property-list"   select="concat($complete-property-list,$new-property-attributes)"/>
+                            <xsl:with-param name="current-style-name"       select="$parent-style-name"/>
+                            <xsl:with-param name="parent-style-name"        select="$new-parent-style-name"/>
+                            <xsl:with-param name="style-family"             select="$style-family"/>
+                        </xsl:call-template>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:when>
+
+            <!--** the specific style (defined by name and family) must be found in the office:styles section -->
+            <xsl:otherwise>
+                <!--*** RECURSION: adding new parent style attributes to the current style ***-->
+                <!--*** adding new parent style attributes to the current style ***-->
+                <xsl:variable name="new-property-attributes">
+                    <xsl:call-template name="get-new-style-attributes">
+                        <xsl:with-param name="new-property-names"           select="$new-property-names"/>
+                        <xsl:with-param name="current-style-name"           select="$current-style-name"/>
+                        <xsl:with-param name="parent-style-name"            select="$parent-style-name"/>
+                    </xsl:call-template>
+                </xsl:variable>
+                <!--*** End CONDITION: the last style parent has already been executed ***-->
+                <xsl:variable name="new-parent-style-name"  select="$office:styles/style:style[@style:family='paragraph' and @style:name=$parent-style-name]/@style:parent-style-name"/>
+                <xsl:choose>
+                    <xsl:when test="string-length($new-parent-style-name)=0">
+                    <!--** no further parent is found, the given parameter property-node is the final style -->
+                        <xsl:value-of select="concat($complete-property-list,$new-property-attributes)"/>
+                    </xsl:when>
+                    <xsl:otherwise>
+                    <!--** further attributes of the parent style must be collected **  -->
+                        <xsl:call-template name="add-parent-style-attributes">
+                            <xsl:with-param name="property-name-list"       select="concat($property-name-list, $new-property-names)"/>
+                            <xsl:with-param name="complete-property-list"   select="concat($complete-property-list,$new-property-attributes)"/>
+                            <xsl:with-param name="current-style-name"       select="$parent-style-name"/>
+                            <xsl:with-param name="parent-style-name"        select="$new-parent-style-name"/>
+                            <xsl:with-param name="style-family"             select="$style-family"/>
+                        </xsl:call-template>
+                    </xsl:otherwise>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <xsl:template name="get-new-style-names">
+        <xsl:param name="property-name-list"/>
+        <xsl:param name="parent-style-name"/>
+        <xsl:param name="current-style-name"/>
+        <!--** where to find the specific style (defined by name and family) wheter in office:automatic-styles or office:styles section -->
+        <xsl:choose>
+            <!--** if the specific style (defined by name and family) can be found in the office:automatic-styles section -->
+            <xsl:when test="$office:automatic-styles/style:style[@style:family='paragraph' and @style:name=$parent-style-name]">
+                <xsl:variable name="parent-property-node" select="$office:automatic-styles/style:style[@style:family='paragraph' and @style:name=$parent-style-name]/style:properties"/>
+
+                <xsl:variable name="new-property-name-list">
+                    <xsl:for-each select="$parent-property-node/@*[not(contains($property-name-list, name()))]">
+                        <xsl:value-of select="name()"/>
+                    </xsl:for-each>
+                </xsl:variable>
+                <xsl:value-of select="$new-property-name-list"/>
+            </xsl:when>
+            <!--** the specific style (defined by name and family) should be found in the office:styles section -->
+            <xsl:otherwise>
+                <xsl:variable name="parent-property-node" select="$office:styles/style:style[@style:family='paragraph' and @style:name=$parent-style-name]/style:properties"/>
+                <xsl:variable name="new-property-name-list">
+                    <xsl:for-each select="$parent-property-node/@*[not(contains($property-name-list, name()))]">
+                        <xsl:value-of select="name()"/>
+                    </xsl:for-each>
+                </xsl:variable>
+                <xsl:value-of select="$new-property-name-list"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <xsl:template name="get-new-style-attributes">
+        <xsl:param name="new-property-names"/>
+        <xsl:param name="current-style-name"/>
+        <xsl:param name="parent-style-name"/>
+
+        <!--** where to find the specific style (defined by name and family) whether in office:automatic-styles or office:styles section -->
+        <xsl:choose>
+            <!--** if the specific style (defined by name and family) can be found in the office:automatic-styles section -->
+            <xsl:when test="$office:automatic-styles/style:style[@style:family='paragraph' and @style:name=$parent-style-name]">
+                <xsl:variable name="parent-property-node" select="$office:automatic-styles/style:style[@style:family='paragraph' and @style:name=$parent-style-name]/style:properties"/>
+                <xsl:variable name="new-property-name-list">
+                    <xsl:call-template name="write-style-properties">
+                        <xsl:with-param name="styleAttributePath"   select="$parent-property-node/@*[contains($new-property-names, name())]"/>
+                    </xsl:call-template>
+                </xsl:variable>
+                <xsl:value-of select="normalize-space($new-property-name-list)"/>
+            </xsl:when>
+            <!--** otherwise the specific style (defined by name and family) should be found in the office:styles section -->
+            <xsl:otherwise>
+                <xsl:variable name="parent-property-node" select="$office:styles/style:style[@style:family='paragraph' and @style:name=$parent-style-name]/style:properties"/>
+                <xsl:variable name="new-property-name-list">
+                    <xsl:call-template name="write-style-properties">
+                        <xsl:with-param name="styleAttributePath"   select="$parent-property-node/@*[contains($new-property-names, name())]"/>
+                    </xsl:call-template>
+                </xsl:variable>
+                <xsl:value-of select="normalize-space($new-property-name-list)"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/style_mapping.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/style_mapping.xsl
new file mode 100644 (file)
index 0000000..a9a858d
--- /dev/null
@@ -0,0 +1,660 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                xmlns:system="http://www.jclark.com/xt/java/java.lang.System"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+
+    <xsl:template name="write-style-properties">
+        <xsl:param name="styleAttributePath"/>
+
+        <xsl:choose>
+            <!--+++++ CSS PROPERTIES  +++++-->
+            <xsl:when test="$outputType = 'CSS_HEADER' or $outputType = 'CSS_INLINED'">
+
+                <xsl:for-each select="$styleAttributePath">
+                <!-- isDebugModeMESSAGE:
+                    <xsl:message> Name:<xsl:value-of select="name()"/> Value:<xsl:value-of select="."/></xsl:message>      -->
+
+
+                    <!-- <!ATTLIST style:properties style:horizontal-pos (from-left|left|center|right|from-inside|inside|outside)#IMPLIED>-->
+                    <!-- 2DO: is inside/from-inside also better showable ? -->
+                    <!-- !!!! 2DO: Still there have to be placed a <br clear='all'/> to disable the flow!!!!-->
+                    <!--           The OOo attribute 'style:number-wrapped-paragraphs' is currently ignored -->
+                    <xsl:choose>
+                        <xsl:when test='name(.)="style:wrap"'>
+                            <xsl:choose>
+                                <xsl:when test='.="left"'>
+                                    <xsl:text>float: right; </xsl:text>
+                                </xsl:when>
+                                <xsl:when test='.="right"'>
+                                    <xsl:text>float: left; </xsl:text>
+                                </xsl:when>
+                            </xsl:choose>
+                        </xsl:when>
+
+                        <xsl:when test='name(.) = "style:horizontal-pos"'>
+                            <xsl:choose>
+                                <xsl:when test='.="left"'>
+                                    <xsl:text>align: left; </xsl:text>
+                                </xsl:when>
+                                <xsl:when test='.="right"'>
+                                    <xsl:text>align: right; </xsl:text>
+                                </xsl:when>
+                                <xsl:when test='.="center"'>
+                                    <xsl:text>align: center; </xsl:text>
+                                </xsl:when>
+                            </xsl:choose>
+                        </xsl:when>
+<!-- results into a bad view (overlapped) in Mozilla 1.0
+                        <xsl:when test='name(.) = "table:align"'>
+                            <xsl:choose>
+                                <xsl:when test='.="left"'>
+                                    <xsl:text>float: right; </xsl:text>
+                                </xsl:when>
+                                <xsl:when test='.="right"'>
+                                    <xsl:text>float: left; </xsl:text>
+                                </xsl:when>
+                            </xsl:choose>
+                        </xsl:when>
+-->
+
+                        <!-- PADDING for all variations: fo:padding, fo:padding-top, fo:padding-bottom, fo:padding-left, fo:padding-right -->
+                        <xsl:when test='contains(name(.),"fo:padding")'>
+                            <xsl:text>padding: </xsl:text>
+                            <xsl:value-of select="."/>
+                            <xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <!--
+                        fo:border
+                        fo:border-top
+                        fo:border-bottom
+                        fo:border-left
+                        fo:border-right
+
+                            At present, all four borders must be set simultaneously by using either
+                            the fo:border property or by attaching all four of the other border
+                            properties to an item set element. In the latter case, if one or more
+                            of the properties is missing their values are assumed to be none. The
+                            only border styles supported are none or hidden, solid, and double. Any
+                            other border style specified is displayed as solid. Transparent borders
+                            are not supported and the border widths thin, medium, and thick are
+                            mapped to lengths. In addition, only some distinct border widths are
+                            supported. Unsupported widths are rounded up to the next supported
+                            width.
+                            If there are no padding properties specified within the same
+                            item set element, a default padding is used for sides that have a
+                            border. A value of 0cm is used for sides without a border.
+                            (cp. wd-so-xml-text.sdw)
+                        -->
+
+<!--2DO START: change measurement equally -->
+                        <xsl:when test='name(.)="fo:border"'>
+                            <xsl:choose>
+                                <!-- changing the distance measure: inch to in -->
+                                <xsl:when test="contains(., 'ch')">
+                                    <xsl:text>border-width:</xsl:text><xsl:value-of select="substring-before(.,'ch ')"/><xsl:text>; </xsl:text>
+                                    <xsl:text>border-style:</xsl:text><xsl:value-of select="substring-before(substring-after(.,'ch '), ' ')"/><xsl:text>; </xsl:text>
+                                    <xsl:text>border-color:</xsl:text><xsl:value-of select="substring-after(substring-after(.,'ch '), ' ')"/><xsl:text>; </xsl:text>
+                                </xsl:when>
+                                <xsl:when test="contains(., 'cm')">
+                                    <xsl:text>border-width:</xsl:text><xsl:value-of select="substring-before(.,' ')"/><xsl:text>; </xsl:text>
+                                    <xsl:text>border-style:</xsl:text><xsl:value-of select="substring-before(substring-after(.,'cm '), ' ')"/><xsl:text>; </xsl:text>
+                                    <xsl:text>border-color:</xsl:text><xsl:value-of select="substring-after(substring-after(.,'cm '), ' ')"/><xsl:text>; </xsl:text>
+                                </xsl:when>
+                                <xsl:when test="contains(., 'pt')">
+                                    <xsl:text>border-width:</xsl:text><xsl:value-of select="substring-before(.,' ')"/><xsl:text>; </xsl:text>
+                                    <xsl:text>border-style:</xsl:text><xsl:value-of select="substring-before(substring-after(.,'pt '), ' ')"/><xsl:text>; </xsl:text>
+                                    <xsl:text>border-color:</xsl:text><xsl:value-of select="substring-after(substring-after(.,'pt '), ' ')"/><xsl:text>; </xsl:text>
+                                </xsl:when>
+                            </xsl:choose>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:border-top"'>
+                            <xsl:text>border-top: </xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:border-bottom"'>
+                            <xsl:text>border-bottom: </xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:border-left"'>
+                            <xsl:text>border-left: </xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:border-right"'>
+                            <xsl:text>border-right: </xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:column-width"'>
+                            <xsl:choose>
+                                <!-- changing the distance measure: inch to in -->
+                                <xsl:when test="contains(., 'ch')">
+                                    <xsl:text>width:</xsl:text><xsl:value-of select="substring-before(.,'ch')"/><xsl:text>; </xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:text>width:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:row-height"'>
+                            <xsl:choose>
+                                <!-- changing the distance measure: inch to in -->
+                                <xsl:when test="contains(., 'ch')">
+                                    <xsl:text>height:</xsl:text><xsl:value-of select="substring-before(.,'ch')"/><xsl:text>; </xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:text>height:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:width"'>
+                            <xsl:choose>
+                                <!-- changing the distance measure: inch to in -->
+                                <xsl:when test="contains(., 'ch')">
+                                    <xsl:text>width:</xsl:text><xsl:value-of select="substring-before(.,'ch')"/><xsl:text>; </xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:text>width:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+<!--2DO END: change measurement equally -->
+                        <xsl:when test='name(.)="fo:font-style"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:font-name"'>
+                            <xsl:text>font-family:</xsl:text>
+                                <xsl:variable name="content" select="."/>
+                                <xsl:value-of select="$office:font-decls/style:font-decl[@style:name=$content]/@fo:font-family"/>
+                            <xsl:text>; </xsl:text>
+                            <xsl:if test="contains($office:font-decls/style:font-decl[@style:name=$content]/@style:font-style-name, 'Italic')">
+                                <xsl:text>font-style:italic; </xsl:text>
+                            </xsl:if>
+                            <xsl:if test="contains($office:font-decls/style:font-decl[@style:name=$content]/@style:font-style-name, 'Bold')">
+                                <xsl:text>font-weight:bold; </xsl:text>
+                            </xsl:if>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:font-weight"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:font-size"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:font-family"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:color"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:margin-left"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:margin-right"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:margin-top"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:margin-bottom"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:line-height"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:text-align"'>
+                            <!-- IMPORTANT is necessary as table cell value alignment is decided by runtime over the valuetype
+                                 Otherwise a table cell style-class will ALWAYS be overwritten by the run-time value -->
+                             <xsl:choose>
+                                <xsl:when test="contains(., 'start')">
+                                    <xsl:text>text-align:left ! important; </xsl:text>
+                                </xsl:when>
+                                <xsl:when test="contains(., 'end')">
+                                    <xsl:text>text-align:right ! important; </xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:text>text-align:</xsl:text><xsl:value-of select='.'/><xsl:text> ! important; </xsl:text>
+                                </xsl:otherwise>
+                             </xsl:choose>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:text-indent"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:text-background-color"'>
+                            <xsl:text>background-color:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="fo:background-color"'>
+                            <xsl:text>background-color:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:background-image"'>
+                            <xsl:text>background-image:url(</xsl:text><xsl:value-of select="@xlink:href"/><xsl:text>); </xsl:text>
+                            <xsl:choose>
+                                <xsl:when test="@style:repeat = 'repeat'">
+                                    <xsl:text>background-repeat:repeat; </xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:text>background-repeat:no-repeat; </xsl:text>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+                        <!-- text-shadow is a CSS2 feature and yet not common used in user-agents -->
+                        <xsl:when test='name(.)="fo:text-shadow"'>
+                            <xsl:value-of select="substring-after(name(.), ':')"/><xsl:text>:</xsl:text><xsl:value-of select="."/><xsl:text>; </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:text-crossing-out"'>
+                            <xsl:if test='not(.="none")'>
+                                <xsl:text>text-decoration:line-through; </xsl:text>
+                            </xsl:if>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:text-underline"'>
+                            <xsl:if test='not(.="none")'>
+                                <xsl:text>text-decoration:underline; </xsl:text>
+                            </xsl:if>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:text-position"'>
+                            <xsl:if test='contains(., "sub")'>
+                                <xsl:text>vertical-align:sub; </xsl:text>
+                            </xsl:if>
+                            <xsl:if test='contains(., "sup")'>
+                                <xsl:text>vertical-align:sup; </xsl:text>
+                            </xsl:if>
+                        </xsl:when>
+                        <!-- isDebugModeMESSAGE:
+                        <xsl:otherwise>
+                                <xsl:message>No transformation implemented for attribute-typ <xsl:value-of select="name(.)"/></xsl:message>
+                        </xsl:otherwise>-->
+                    </xsl:choose>
+                </xsl:for-each>
+            </xsl:when>
+            <!--+++++ PALM 3.2 SUBSET AND WAP PROPERTIES  +++++-->
+            <xsl:otherwise>
+                <xsl:for-each select="$styleAttributePath">
+                    <!-- isDebugModeMESSAGE:
+                    <xsl:message> Name:<xsl:value-of select="name()"/> Value:<xsl:value-of select="."/></xsl:message>      -->
+
+                    <!-- BUG WORK AROUND:
+                    Due to a bug in the XT Processor, it is not possible to create serveral elements in variable and search over them,
+                    after explicit conversion to nodeset
+                    This generated sting identifier shall be later changed back to a set of elements
+                    -->
+                    <xsl:choose>
+                        <!--*** FORMAT ATTRIBUTES ***-->
+
+                        <!-- Italic -->
+                        <xsl:when test='name(.)="fo:font-style"'>
+                            <xsl:if test="contains(., 'italic') or contains(., 'oblique')">
+                                <xsl:text>italic, </xsl:text>
+                            </xsl:if>
+                        </xsl:when>
+
+                        <!-- Boldface -->
+                        <xsl:when test='name(.)="fo:font-weight"'>
+                            <xsl:if test="contains(., 'bold') or contains(., 'bolder')">
+                                <xsl:text>bold, </xsl:text>
+                            </xsl:if>
+                        </xsl:when>
+
+                        <!-- Underline -->
+                        <xsl:when test='name(.)="style:text-underline"'>
+                            <xsl:text>underline, </xsl:text>
+                        </xsl:when>
+
+                        <!-- Alignment -->
+                        <xsl:when test='name(.)="fo:text-align"'>
+                             <xsl:choose>
+                                <xsl:when test="contains(., 'start')">
+                                    <xsl:text>align:left, </xsl:text>
+                                </xsl:when>
+                                <xsl:when test="contains(., 'end')">
+                                    <xsl:text>align:right, </xsl:text>
+                                </xsl:when>
+                                <xsl:when test="contains(., 'center')">
+                                    <xsl:text>align:center, </xsl:text>
+                                </xsl:when>
+                             </xsl:choose>
+                        </xsl:when>
+
+                        <!-- strikethrough -->
+                        <xsl:when test='name(.)="style:text-crossing-out"'>
+                            <xsl:text>strike, </xsl:text>
+                        </xsl:when>
+
+                        <!-- Font - size (Palm: emulator transformed sizes to available set (e.g. 30 to (probably) 9)-->
+                        <xsl:when test='name(.)="fo:font-size"'>
+                            <xsl:text>size:</xsl:text><xsl:value-of select="."/><xsl:text>:size, </xsl:text>
+                        </xsl:when>
+
+                        <!-- Font - Color (PALM: but mostly only 2 available)
+                            black (#000000)
+                            gray (#808080)(rendered as dark gray)
+                            silver (#C0C0C0)(rendered as light gray)
+                            white (#FFFFFF)-->
+                        <xsl:when test='name(.)="fo:color"'>
+                            <xsl:choose>
+                                <xsl:when test="contains(. , '#FFFFFF') or contains(. , '#ffffff') or contains(. , 'white') or contains(. , 'WHITE')">
+                                    <xsl:text>color:#FFFFFF, </xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:text>color:#000000, </xsl:text>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+
+
+                        <!--*** TABLE ATTRIBUTES ***-->
+                        <xsl:when test='name(.)="fo:font-size"'>
+                            <xsl:text>size:</xsl:text><xsl:value-of select="."/><xsl:text>:size, </xsl:text>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:column-width"'>
+                            <xsl:choose>
+                                <!-- changing the distance measure: inch to in -->
+                                <xsl:when test="contains(., 'ch')">
+                                    <xsl:text>width:</xsl:text><xsl:value-of select="substring-before(.,'ch')"/><xsl:text>:width, </xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:text>width:</xsl:text><xsl:value-of select="."/><xsl:text>:width; </xsl:text>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:row-height"'>
+                            <xsl:choose>
+                                <!-- changing the distance measure: inch to in -->
+                                <xsl:when test="contains(., 'ch')">
+                                    <xsl:text>height:</xsl:text><xsl:value-of select="substring-before(.,'ch')"/><xsl:text>:height; </xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:text>height:</xsl:text><xsl:value-of select="."/><xsl:text>:height; </xsl:text>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+                        <xsl:when test='name(.)="style:width"'> <!--earlier fo:width-->
+                            <xsl:choose>
+                                <!-- changing the distance measure: inch to in -->
+                                <xsl:when test="contains(., 'ch')">
+                                    <xsl:text>width:</xsl:text><xsl:value-of select="substring-before(.,'ch')"/><xsl:text>:width; </xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:text>width:</xsl:text><xsl:value-of select="."/><xsl:text>:width; </xsl:text>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:when>
+                    </xsl:choose>
+                </xsl:for-each>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+<!-- 2DO: NAMING CONVENTION variable are written with '-' instead of case-sensitive writing -->
+
+
+
+    <!-- ***** MEASUREMENT CONVERSIONS *****
+
+     * 1 centimeter = 10 mm
+
+     * 1 inch = 25.4 mm
+        While the English have already seen the light (read: the metric system), the US
+        remains loyal to this medieval system.
+
+     * 1 didot point = 0.376065 mm
+            The didot system originated in France but was used in most of Europe
+
+     * 1 pica point = 0.35146 mm
+            The Pica points system was developed in England and is used in Great-Britain and the US.
+
+     * 1 PostScript point = 0.35277138 mm
+            When Adobe created PostScript, they added their own system of points.
+            There are exactly 72 PostScript points in 1 inch.
+
+     * 1 pixel = 0.26458333.. mm   (by 96 dpi)
+            Most pictures have the 96 dpi resolution, but the dpi variable may vary by stylesheet parameter
+    -->
+
+
+    <!-- changing measure to mm -->
+    <xsl:template name="convert2mm">
+        <xsl:param name="value"/>
+
+        <xsl:param name="centimeter-in-mm"          select="10"/>
+        <xsl:param name="inch-in-mm"                select="25.4"/>
+        <xsl:param name="didot-point-in-mm"         select="0.376065"/>
+        <xsl:param name="pica-point-in-mm"          select="0.35146"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($value, 'cm')">
+                <xsl:value-of select="round(number(substring-before($value,'cm' )) * $centimeter-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'in')">
+                <xsl:value-of select="round(number(substring-before($value,'in' )) * $inch-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'dpt')">
+                <xsl:value-of select="round(number(substring-before($value,'dpt')) * $didot-point-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'ppt')">
+                <xsl:value-of select="round(number(substring-before($value,'ppt')) * $pica-point-in-mm)"/>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:value-of select="$value"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- changing measure to cm -->
+    <xsl:template name="convert2cm">
+        <xsl:param name="value"/>
+
+        <xsl:param name="centimeter-in-mm"          select="10"/>
+        <xsl:param name="inch-in-mm"                select="25.4"/>
+        <xsl:param name="didot-point-in-mm"         select="0.376065"/>
+        <xsl:param name="pica-point-in-mm"          select="0.35146"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($value, 'mm')">
+                <xsl:value-of select="round(number(substring-before($value, 'mm')) div $centimeter-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'in')">
+                <xsl:value-of select="round(number(substring-before($value, 'in')) div $centimeter-in-mm * $inch-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'dpt')">
+                <xsl:value-of select="round(number(substring-before($value,'dpt')) div $centimeter-in-mm * $didot-point-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'ppt')">
+                <xsl:value-of select="round(number(substring-before($value,'ppt')) div $centimeter-in-mm * $pica-point-in-mm)"/>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:value-of select="$value"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+    <!-- changing measure to inch (cp. section comment) -->
+    <xsl:template name="convert2inch">
+        <xsl:param name="value"/>
+
+        <xsl:param name="centimeter-in-mm"          select="10"/>
+        <xsl:param name="inch-in-mm"                select="25.4"/>
+        <xsl:param name="didot-point-in-mm"         select="0.376065"/>
+        <xsl:param name="pica-point-in-mm"          select="0.35146"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($value, 'mm')">
+                <xsl:value-of select="round(number(substring-before($value, 'mm')) div $inch-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'cm')">
+                <xsl:value-of select="round(number(substring-before($value, 'cm')) div $inch-in-mm * $centimeter-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'dpt')">
+                <xsl:value-of select="round(number(substring-before($value,'dpt')) div $inch-in-mm * $didot-point-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'ppt')">
+                <xsl:value-of select="round(number(substring-before($value,'ppt')) div $inch-in-mm * $pica-point-in-mm)"/>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:value-of select="$value"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- changing measure to dpt (cp. section comment) -->
+    <xsl:template name="convert2dpt">
+        <xsl:param name="value"/>
+
+        <xsl:param name="centimeter-in-mm"          select="10"/>
+        <xsl:param name="inch-in-mm"                select="25.4"/>
+        <xsl:param name="didot-point-in-mm"         select="0.376065"/>
+        <xsl:param name="pica-point-in-mm"          select="0.35146"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($value, 'mm')">
+                <xsl:value-of select="round(number(substring-before($value, 'mm')) div $didot-point-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'cm')">
+                <xsl:value-of select="round(number(substring-before($value, 'cm')) div $didot-point-in-mm * $centimeter-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'in')">
+                <xsl:value-of select="round(number(substring-before($value, 'in')) div $didot-point-in-mm * $inch-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'ppt')">
+                <xsl:value-of select="round(number(substring-before($value,'ppt')) div $didot-point-in-mm * $pica-point-in-mm)"/>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:value-of select="$value"/>
+            </xsl:otherwise>
+        </xsl:choose>
+
+    </xsl:template>
+
+
+    <!-- changing measure to ppt (cp. section comment) -->
+    <xsl:template name="convert2ppt">
+        <xsl:param name="value"/>
+
+        <xsl:param name="centimeter-in-mm"          select="10"/>
+        <xsl:param name="inch-in-mm"                select="25.4"/>
+        <xsl:param name="didot-point-in-mm"         select="0.376065"/>
+        <xsl:param name="pica-point-in-mm"          select="0.35146"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($value, 'mm')">
+                <xsl:value-of select="round(number(substring-before($value, 'mm')) div $pica-point-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'cm')">
+                <xsl:value-of select="round(number(substring-before($value, 'cm')) div $pica-point-in-mm * $centimeter-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'in')">
+                <xsl:value-of select="round(number(substring-before($value, 'in')) div $pica-point-in-mm * $inch-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'dpt')">
+                <xsl:value-of select="round(number(substring-before($value,'dpt')) div $pica-point-in-mm * $didot-point-in-mm)"/>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:value-of select="$value"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- changing measure to pixel by via parameter provided dpi (dots per inch) standard factor (cp. section comment) -->
+    <xsl:template name="convert2pixel">
+        <xsl:param name="value"/>
+
+        <xsl:param name="centimeter-in-mm"          select="10"/>
+        <xsl:param name="inch-in-mm"                select="25.4"/>
+        <xsl:param name="didot-point-in-mm"         select="0.376065"/>
+        <xsl:param name="pica-point-in-mm"          select="0.35146"/>
+        <xsl:param name="pixel-in-mm"               select="$inch-in-mm div $dpi"/>
+
+        <xsl:choose>
+            <xsl:when test="contains($value, 'mm')">
+                <xsl:value-of select="round(number(substring-before($value, 'mm')) div $pixel-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'cm')">
+                <xsl:value-of select="round(number(substring-before($value, 'cm')) div $pixel-in-mm * $centimeter-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'in')">
+                <xsl:value-of select="round(number(substring-before($value, 'in')) div $pixel-in-mm * $inch-in-mm)"/>
+            </xsl:when>
+            <xsl:when test="contains($value, 'dpt')">
+                <xsl:value-of select="round(number(substring-before($value,'dpt')) div $pixel-in-mm * $didot-point-in-mm)"/>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:value-of select="$value"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/table.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/table.xsl
new file mode 100644 (file)
index 0000000..36339ed
--- /dev/null
@@ -0,0 +1,328 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                xmlns:system="http://www.jclark.com/xt/java/java.lang.System"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+
+    <!-- table row handling -->
+    <xsl:include href="table_rows.xsl"/>
+    <!-- table column handling -->
+    <xsl:include href="table_columns.xsl"/>
+    <!-- table cell handling -->
+    <xsl:include href="table_cells.xsl"/>
+
+
+
+    <!-- ******************* -->
+    <!-- *** main table  *** -->
+    <!-- ******************* -->
+
+    <xsl:template match="table:table | table:sub-table">
+        <xsl:param name="collectedGlobalData"/>
+
+        <!-- a table will only be created if the "scenario" is active -->
+        <xsl:if test="string-length(table:scenario/@table:is-active) = 0">
+            <!-- collecting all visible "table:table-row" elements of the table -->
+            <xsl:variable name="allVisibleTableRows" select="table:table-row[not(@table:visibility = 'collapse' or @table:visibility = 'filter')]
+                    |    table:table-header-rows/descendant::table:table-row[not(@table:visibility = 'collapse' or @table:visibility = 'filter')]
+                    |    table:table-row-group/descendant::table:table-row[not(@table:visibility = 'collapse' or @table:visibility = 'filter')]"/>
+            <xsl:choose>
+                <!-- for all but WAP/WML devices a table border check is done (cp. "check-for-table-border") -->
+                <xsl:when test="not($outputType = 'WML')">
+
+                    <!-- As the alignment of a table is by 'align' attribut is deprecated and as the CSS 'float' attribute not well displayed,
+                         we do a little trick by encapsulating the table with a aligned 'div' element-->
+                    <xsl:variable name="table-alignment" select="$office:automatic-styles/style:style[@style:name = current()/@table:style-name]/style:properties/@table:align"/>
+
+                    <xsl:choose>
+                        <xsl:when test="string-length($table-alignment) != 0">
+                            <xsl:element name="div">
+                                <xsl:attribute name="align">
+                                    <xsl:choose>
+                                        <xsl:when test='$table-alignment="left" or $table-alignment="margins"'>
+                                                <xsl:text>left</xsl:text>
+                                        </xsl:when>
+                                        <xsl:when test='$table-alignment="right"'>
+                                            <xsl:text>right</xsl:text>
+                                        </xsl:when>
+                                        <xsl:when test='$table-alignment="center"'>
+                                            <xsl:text>center</xsl:text>
+                                        </xsl:when>
+                                    </xsl:choose>
+                                </xsl:attribute>
+                                <xsl:element name="table">
+
+                                    <xsl:apply-templates select="@table:style-name">
+                                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                    </xsl:apply-templates>
+
+                                    <!-- workaround, set table border attribut if any cell-border exists
+                                    <xsl:call-template name="check-for-table-border">
+                                        <xsl:with-param name="allVisibleTableRows" select="$allVisibleTableRows"/>
+                                    </xsl:call-template> -->
+                                    <xsl:call-template name="create-column-style-variable">
+                                        <xsl:with-param name="allVisibleTableRows" select="$allVisibleTableRows"/>
+                                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                    </xsl:call-template>
+                                </xsl:element>
+                            </xsl:element>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <xsl:element name="table">
+                                <xsl:apply-templates select="@table:style-name">
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:apply-templates>
+
+                                <!-- workaround, set table border attribut if any cell-border exists
+                                <xsl:call-template name="check-for-table-border">
+                                    <xsl:with-param name="allVisibleTableRows" select="$allVisibleTableRows"/>
+                                </xsl:call-template>  -->
+                                <xsl:call-template name="create-column-style-variable">
+                                    <xsl:with-param name="allVisibleTableRows" select="$allVisibleTableRows"/>
+                                     <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:call-template>
+                            </xsl:element>
+                        </xsl:otherwise>
+                    </xsl:choose>
+
+                </xsl:when>
+                <xsl:otherwise>
+                <!-- for WML devices only ASCII table are written as tables are not implemented widley.
+                     Beginning from 'repeat-write-row' the templates are handled by the table_wml.xsl stylesheet -->
+                    <xsl:call-template name="create-column-style-variable">
+                        <xsl:with-param name="collectedGlobalData"   select="$collectedGlobalData"/>
+                        <xsl:with-param name="allVisibleTableRows"  select="$allVisibleTableRows"/>
+                    </xsl:call-template>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:if>
+    </xsl:template>
+
+
+
+    <xsl:template name="create-column-style-variable">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="allVisibleTableRows"/>
+
+        <!-- all columns of the table -->
+        <xsl:variable name="allTableColumns" select="table:table-column |
+                                                     table:table-column-group/descendant::table:table-column |
+                                                     table:table-header-columns/descendant::table:table-column"/>
+        <!-- allColumnStyleEntries: Containing all columns of the table, hidden and viewed.
+            - if a column is hidden, it contains the hidden attribute, otherwise the style-properties will be stored
+            - if a column is being repeated, each repeated column is explicitly written as entry in this variable.
+              Later (during template "write-cell") the style of the column will be mixed with the cell-style by using
+              the position() of the column entry and comparing it with the iterating cell number. -->
+        <xsl:variable name="allColumnStyleEntries-RTF">
+            <xsl:call-template name="adding-column-styles-entries">
+                <xsl:with-param name="collectedGlobalData"   select="$collectedGlobalData"/>
+                <xsl:with-param name="allTableColumns"      select="$allTableColumns"/>
+            </xsl:call-template>
+        </xsl:variable>
+
+        <xsl:choose>
+            <xsl:when test="function-available('xt:node-set')">
+                <xsl:call-template name="create-table">
+                    <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                    <xsl:with-param name="allVisibleTableRows"      select="$allVisibleTableRows"/>
+                    <xsl:with-param name="allColumnStyleEntries"    select="xt:node-set($allColumnStyleEntries-RTF)"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="function-available('xalan:nodeset')">
+                <xsl:call-template name="create-table">
+                    <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                    <xsl:with-param name="allVisibleTableRows"      select="$allVisibleTableRows"/>
+                    <xsl:with-param name="allColumnStyleEntries"    select="xalan:nodeset($allColumnStyleEntries-RTF)"/>
+                </xsl:call-template>
+            </xsl:when>
+        </xsl:choose>
+
+    </xsl:template>
+
+
+
+    <xsl:template name="create-table">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="allVisibleTableRows"/>
+        <xsl:param name="allColumnStyleEntries"/>
+
+
+        <!-- Some Office Calc documents simulate a background by repeating the last cell until end of space
+             (The value of "table:number-columns-repeated" is enourmous). Writing out all these cells would be fatal.
+             Therefore, this global variable shows us the longest row with content.
+
+        Earlier only the viewable columns were listed, but it is easier to handle with all columns:
+        <xsl:variable name="maxRowLength" select="count($allColumnStyleEntries/column-style-entry[not(@column-hidden-flag)])"/> -->
+        <xsl:variable name="maxRowLength" select="count($allColumnStyleEntries/column-style-entry)"/>
+
+
+        <!--isDebugMode-START-->
+        <xsl:if test="$isDebugMode">
+            <xsl:message>maxRowLength: <xsl:value-of select="$maxRowLength"/></xsl:message>
+            <xsl:variable name="numberOfHiddenColumns" select="count($allColumnStyleEntries/column-style-entry[@column-hidden-flag])"/>
+            <xsl:message>numberOfHiddenColumns: <xsl:value-of select="$numberOfHiddenColumns"/></xsl:message>
+            <xsl:call-template name="table-debug-allColumnStyleEntries">
+                <xsl:with-param name="allColumnStyleEntries" select="$allColumnStyleEntries"/>
+            </xsl:call-template>
+        </xsl:if>
+        <!--isDebugMode-END-->
+        <xsl:choose>
+            <xsl:when test="$outputType = 'WML'">
+                <!-- matching all rows - we can not use xsl:apply-template with a node-set parameter as by a bug in XT (James Clark)
+                     (here: allColumnStyleEntries) will be interpreted as a result tree fragment, where no search expression (XPath) can be used
+                     2DO:CHECK WITH XALAN-->
+                <xsl:for-each select="$allVisibleTableRows">
+                    <xsl:call-template name="wml-repeat-write-row">
+                        <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                        <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                        <xsl:with-param name="number-rows-repeated"     select="@table:number-rows-repeated"/>
+                        <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                    </xsl:call-template>
+                </xsl:for-each>
+            </xsl:when>
+            <xsl:otherwise>
+                <!-- matching all rows - we can not use xsl:apply-template with a node-set parameter as by a bug in XT (James Clark)
+                     (here: allColumnStyleEntries) will be interpreted as a result tree fragment, where no search expression (XPath) can be used
+                     2DO:CHECK WITH XALAN -->
+                <xsl:for-each select="$allVisibleTableRows">
+                    <xsl:call-template name="repeat-write-row">
+                        <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                        <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                        <xsl:with-param name="number-rows-repeated"     select="@table:number-rows-repeated"/>
+                        <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                    </xsl:call-template>
+                </xsl:for-each>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+
+
+    <!-- **************************** -->
+    <!-- *** HELPER: table border *** -->
+    <!-- **************************** -->
+
+    <!-- only one table border for HTML4 or CSS devices which contain one or more 'fo:border-top' attributes (pars pro toto, if one exist the other usually exist, too) -->
+    <!-- this was a work-around for the netscape 4.xx but not longer necessary for Mozilla -->
+    <xsl:template name="check-for-table-border">
+        <xsl:param name="allVisibleTableRows"/>
+
+        <xsl:variable name="startTime">
+            <xsl:if test="$isDebugMode and not($isJavaDisabled)">
+                <xsl:choose>
+                    <xsl:when test="function-available('system:current-time-millis')">
+                        <xsl:value-of select="system:current-time-millis()"/>
+                    </xsl:when>
+                    <xsl:when test="function-available('java:java.lang.System.currentTimeMillis')">
+                        <xsl:value-of select="java:java.lang.System.currentTimeMillis()"/>
+                    </xsl:when>
+                </xsl:choose>
+            </xsl:if>
+        </xsl:variable>
+
+        <!-- checks if one cell (table:table-cell) of the rows of this table (allVisibleTableRows) contains a border style (i.e. fo:border-top)
+             If only one single border element exist, the whole table will gets pre-defined borders (simple heuristic for better browser display) -->
+        <xsl:if test="$allVisibleTableRows/table:table-cell[@table:style-name=/*/*/style:style[style:properties/@fo:border-top]/@style:name]">
+            <xsl:attribute name="border">1</xsl:attribute>
+            <xsl:attribute name="bordercolor">#000000</xsl:attribute>
+            <xsl:attribute name="cellpadding">2</xsl:attribute>
+            <xsl:attribute name="cellspacing">0</xsl:attribute>
+            <xsl:attribute name="page-break-inside">page-break-inside:avoid</xsl:attribute>
+        </xsl:if>
+
+
+        <!-- check the time for borderchecking (debug)-->
+        <xsl:if test="$isDebugMode and not($isJavaDisabled)">
+            <xsl:variable name="endTime">
+                <xsl:choose>
+                    <xsl:when test="function-available('system:current-time-millis')">
+                        <xsl:value-of select="system:current-time-millis()"/>
+                    </xsl:when>
+                    <xsl:when test="function-available('java:java.lang.System.currentTimeMillis')">
+                        <xsl:value-of select="java:java.lang.System.currentTimeMillis()"/>
+                    </xsl:when>
+                </xsl:choose>
+            </xsl:variable>
+            <xsl:message>Time for checking BorderStyle: <xsl:value-of select="($endTime - $startTime)"/> ms</xsl:message>
+        </xsl:if>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/table_cells.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/table_cells.xsl
new file mode 100644 (file)
index 0000000..4671ea9
--- /dev/null
@@ -0,0 +1,484 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                xmlns:system="http://www.jclark.com/xt/java/java.lang.System"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+
+    <!-- *********************************** -->
+    <!-- *** write repeating table cells *** -->
+    <!-- *********************************** -->
+
+
+    <!-- matching cells to give out -> covered table cells are not written out -->
+    <xsl:template match="table:table-cell">
+        <xsl:param name="collectedGlobalData"/>
+        <!-- position of the current input cell to get the correct colum style (hidden are also counted)-->
+        <xsl:param name="allColumnStyleEntries"/>
+        <xsl:param name="maxRowLength"/>
+
+        <xsl:if test="$isDebugMode">
+            <xsl:message>
+--------------> table:table-cell has been entered with node value: <xsl:value-of select="."/></xsl:message>
+            <xsl:message>table:number-columns-repeated: -<xsl:value-of select="@table:number-columns-repeated"/>-</xsl:message>
+        </xsl:if>
+
+        <xsl:call-template name="create-column-position-variable">
+            <!-- position of the current input cell to get the correct colum style (hidden are also counted)-->
+            <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+            <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+            <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+        </xsl:call-template>
+
+    </xsl:template>
+
+
+
+    <xsl:template name="create-column-position-variable">
+        <!-- position of the current input cell to get the correct colum style (hidden are also counted)-->
+        <xsl:param name="allColumnStyleEntries"/>
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="maxRowLength"/>
+
+        <!-- column position needed for styles, esp. for column-hidden-flag -->
+        <xsl:variable name="preceding-columns">
+            <xsl:for-each select="preceding-sibling::*">
+                <xsl:element name="quantity">
+                    <xsl:choose>
+                        <xsl:when test="string-length(@table:number-columns-repeated) = 0">1</xsl:when>
+                        <xsl:otherwise><xsl:value-of select="@table:number-columns-repeated"/></xsl:otherwise>
+                    </xsl:choose>
+                </xsl:element>
+            </xsl:for-each>
+        </xsl:variable>
+
+        <xsl:choose>
+            <xsl:when test="function-available('xt:node-set')">
+                <xsl:call-template name="create-table-cell">
+                    <!-- position of the current input cell to get the correct colum style (hidden are also counted)-->
+                    <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                    <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                    <xsl:with-param name="column-position"          select="sum(xt:node-set($preceding-columns)/quantity) + 1"/>
+                    <xsl:with-param name="collectedGlobalData"      select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:when test="function-available('xalan:nodeset')">
+                <xsl:call-template name="create-table-cell">
+                    <!-- position of the current input cell to get the correct colum style (hidden are also counted)-->
+                    <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                    <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                    <xsl:with-param name="column-position"          select="sum(xalan:nodeset($preceding-columns)/quantity) + 1"/>
+                    <xsl:with-param name="collectedGlobalData"      select="$collectedGlobalData"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:otherwise>
+                <xsl:element name="NodeSetFunctionNotAvailable"/>
+                <xsl:call-template name="create-table-cell"/>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <xsl:template name="create-table-cell">
+        <!-- position of the current input cell to get the correct colum style (hidden are also counted)-->
+        <xsl:param name="allColumnStyleEntries"/>
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="maxRowLength"/>
+        <xsl:param name="column-position"/>
+
+
+        <xsl:if test="$isDebugMode">
+            <xsl:message>NEW VALUE: column-position: -<xsl:value-of select="$column-position"/>-</xsl:message>
+        </xsl:if>
+
+
+        <!-- a hidden column will give out nothing -->
+        <xsl:if test="not($allColumnStyleEntries/column-style-entry[position() = $column-position]/@column-hidden-flag)">
+            <xsl:choose>
+                <!-- when the columns are not repeated the next column-positions raises up to 1, otherwise up to the amount of repeated columns -->
+                <xsl:when test="@table:number-columns-repeated">
+                    <!-- writes multiple entries of a cell -->
+                    <xsl:call-template name="repeat-write-cell">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                        <xsl:with-param name="column-position"          select="$column-position"/>
+                        <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                        <xsl:with-param name="number-columns-repeated"  select="@table:number-columns-repeated"/>
+                    </xsl:call-template>
+                </xsl:when>
+                <xsl:otherwise>
+                    <!-- writes an entry of a cell -->
+                    <xsl:call-template name="write-cell">
+                        <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                        <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                        <xsl:with-param name="column-position"          select="$column-position"/>
+                        <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                    </xsl:call-template>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:if>
+
+    </xsl:template>
+
+
+
+    <xsl:template name="repeat-write-cell">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="allColumnStyleEntries"/>
+        <xsl:param name="column-position"/>
+        <xsl:param name="maxRowLength"/>
+        <xsl:param name="number-columns-repeated"/>
+
+        <xsl:choose>
+            <!-- 2DO: This is the current workaround against the background simulation by an 'endless' repeating cell -->
+            <xsl:when test="$number-columns-repeated > 1 and $maxRowLength > $column-position">
+
+                <xsl:if test="$isDebugMode">
+                    <xsl:message>+++++++++ starting cell writing +++++++++</xsl:message>
+                    <xsl:message>number-columns-repeated: -<xsl:value-of select="$number-columns-repeated"/>-</xsl:message>
+                    <xsl:message>maxRowLength: -<xsl:value-of select="$maxRowLength"/>-</xsl:message>
+                    <xsl:message>column-position: -<xsl:value-of select="$column-position"/>-</xsl:message>
+                </xsl:if>
+
+                <!-- writes an entry of a cell -->
+                <xsl:call-template name="write-cell">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                    <xsl:with-param name="column-position"          select="$column-position"/>
+                    <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                </xsl:call-template>
+                <!-- repeat calling this method until all elements written out -->
+                <xsl:if test="$isDebugMode">
+                    <xsl:message>+++++++++ cell repetition +++++++++</xsl:message>
+                </xsl:if>
+                <xsl:call-template name="repeat-write-cell">
+                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                    <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                    <xsl:with-param name="column-position"          select="$column-position + 1"/>
+                    <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                    <xsl:with-param name="number-columns-repeated"  select="$number-columns-repeated - 1"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:otherwise>
+                <!-- 2DO: This is the current workaround against the background simulation by an 'endless' repeating cell -->
+                <!--      When the maxRowLength is reached a last entry of a cell is written -->
+                <xsl:call-template name="write-cell">
+                    <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                    <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                    <xsl:with-param name="column-position"          select="$column-position"/>
+                    <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <xsl:template name="write-cell">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="allColumnStyleEntries"/>
+        <xsl:param name="column-position"/>
+        <xsl:param name="maxRowLength"/>
+
+
+        <xsl:if test="$isDebugMode">
+            <xsl:message>WriteTest -> If nothing between '-' write cell -<xsl:value-of select="$allColumnStyleEntries/column-style-entry[position() = $column-position]/@column-hidden-flag"/>-</xsl:message>
+        </xsl:if>
+
+            <xsl:if test="$allColumnStyleEntries/column-style-entry[position() = $column-position]/@column-hidden-flag">
+                <xsl:if test="$isDebugMode">
+                    <xsl:message>TABLE COLUMN is hidden!</xsl:message>
+                </xsl:if>
+            </xsl:if>
+
+        <xsl:choose>
+            <!-- a hidden column will give out nothing -->
+            <xsl:when test="$allColumnStyleEntries/column-style-entry[position() = $column-position]/@column-hidden-flag">
+                <xsl:if test="$isDebugMode">
+                    <xsl:message>TABLE COLUMN is hidden!</xsl:message>
+                </xsl:if>
+            </xsl:when>
+
+            <!-- NOT a hidden column -->
+            <xsl:otherwise>
+
+                <!-- a table is a table header, when it has a "table:table-header-rows" ancestor -->
+                <xsl:variable name="tableDataType">
+                    <xsl:choose>
+                        <xsl:when test="ancestor::table:table-header-rows">
+                            <xsl:text>th</xsl:text>
+                        </xsl:when>
+                        <xsl:otherwise>
+                            <xsl:text>td</xsl:text>
+                        </xsl:otherwise>
+                    </xsl:choose>
+                </xsl:variable>
+
+                <xsl:choose>
+                    <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+                    <xsl:when test="$outputType = 'CSS_HEADER'">
+                        <xsl:element name="{$tableDataType}">
+
+                            <xsl:if test="$isDebugMode">
+                                <xsl:message>
+*****************************************'<xsl:value-of select="$tableDataType"/>' element has been added!</xsl:message>
+                            </xsl:if>
+
+                            <xsl:if test="@table:number-columns-spanned">
+                                <xsl:attribute name="colspan">
+                                    <xsl:value-of select="@table:number-columns-spanned"/>
+                                </xsl:attribute>
+                            </xsl:if>
+                            <xsl:if test="@table:number-rows-spanned">
+                                <xsl:attribute name="rowspan">
+                                    <xsl:value-of select="@table:number-rows-spanned"/>
+                                </xsl:attribute>
+                            </xsl:if>
+
+
+
+                            <!-- *** the cell-style *** -->
+                            <!-- The cell style has no conclusion with the column style, so we switch the order/priorities due to browser issues
+
+                                The cell-style depends on two attributes:
+
+                                1) table:style-name - the style properties of cell. When they exist, a default alignement (cp. below) will be added for the
+                                                      case of no alignment in the style exist.
+
+                                2) table:value-type - the value type of the table-cell giving the default alignments.
+                                                      By default a string value is left aligned, all other are aligned:right.
+                            -->
+                            <xsl:choose>
+                                <xsl:when test="@table:style-name">
+                                    <xsl:attribute name="style">
+
+                                        <!-- CELL-STYLE: alignment by table:value-type (without existing table:style-name)-->
+                                        <xsl:variable name="cellStyle" select="$collectedGlobalData/allstyles/*[name()=current()/@table:style-name]"/>
+                                        <xsl:choose>
+                                            <xsl:when test="string-length($cellStyle) > 0 and not(contains($cellStyle, 'text-align'))">
+                                                <!-- CELL-STYLE: alignment by table:value-type -->
+                                                <!-- no alignment in the cell style, the alignment based on the table:value-type will be added -->
+                                                <xsl:choose>
+                                                    <xsl:when test="@table:value-type and not(@table:value-type = 'string')">
+                                                        <xsl:value-of select="concat($collectedGlobalData/allstyles/*[name()=current()/@table:style-name], 'text-align:right; ')"/>
+                                                    </xsl:when>
+                                                    <xsl:otherwise>
+                                                        <xsl:value-of select="concat($collectedGlobalData/allstyles/*[name()=current()/@table:style-name], 'text-align:left; ')"/>
+                                                    </xsl:otherwise>
+                                                </xsl:choose>
+                                            </xsl:when>
+                                            <xsl:otherwise>
+                                                <!-- CELL-STYLE: alignment by table:value-type -->
+                                                <!-- no CSS style properties exist, only alignment from the table:value-type will be used -->
+                                                <xsl:choose>
+                                                    <xsl:when test="@table:value-type and not(@table:value-type = 'string')">text-align:right; </xsl:when>
+                                                    <xsl:otherwise>text-align:left; </xsl:otherwise>
+                                                </xsl:choose>
+                                            </xsl:otherwise>
+                                        </xsl:choose>
+
+                                         <!-- column-style (disjunct of cell style -->
+                                         <!-- 2DO: only absolut styles are supported, relative styles (i.e. 'style:rel-column-width' e.g. with value "8933*" are ignored.
+                                              Issue: browsers (not sure if CSS) does not support the '*' relationship, only the '%', where the sum is always '100'!
+                                              For this, it is easier to work on with the absolute values, instead of calculating the values for 100% -->
+                                         <xsl:value-of select="$allColumnStyleEntries/column-style-entry[position()=$column-position]"/>
+                                    </xsl:attribute>
+                                    <!-- CELL-STYLE: table:style-name -->
+                                    <xsl:attribute name="class">
+                                        <xsl:value-of select="translate(@table:style-name, '. %()/\', '')"/>
+                                    </xsl:attribute>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <xsl:attribute name="style">
+                                        <!-- CELL-STYLE: alignment by table:value-type (without existing table:style-name)-->
+                                        <!-- no table:style-name exist, only alignment from the table:value-type will be used -->
+                                        <xsl:choose>
+                                            <xsl:when test="@table:value-type and not(@table:value-type = 'string')">
+                                                text-align:right;
+                                            </xsl:when>
+                                            <xsl:otherwise>
+                                                text-align:left;
+                                            </xsl:otherwise>
+                                        </xsl:choose>
+                                    </xsl:attribute>
+                                </xsl:otherwise>
+                            </xsl:choose>
+
+                            <xsl:choose>
+                                <!-- In case of no cell content a non-breakable space will be inserted
+                                     to make the browser show the table-cell grid -->
+                                <xsl:when test="not(child::text()) and not(child::*)">
+                                    <xsl:text> &#160;</xsl:text>
+                                </xsl:when>
+                                <xsl:otherwise>
+                                    <!-- *** the column-style *** -->
+                                    <!--  the column style has no conclusion with the cell style, so we switch the order/priorities due to browser issues-->
+                                    <xsl:element name="span">
+                                        <xsl:attribute name="class">
+                                            <xsl:value-of select="$allColumnStyleEntries/column-style-entry[position() = $column-position]/@style-name"/>
+                                        </xsl:attribute>
+                                        <xsl:apply-templates>
+                                            <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                        </xsl:apply-templates>
+                                    </xsl:element>
+                                </xsl:otherwise>
+                            </xsl:choose>
+                        </xsl:element>
+                    </xsl:when>
+
+                    <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+                    <xsl:when test="$outputType = 'CSS_INLINED'">
+                        <xsl:element name="{$tableDataType}">
+
+                            <xsl:if test="@table:number-columns-spanned">
+                                <xsl:attribute name="colspan">
+                                    <xsl:value-of select="@table:number-columns-spanned"/>
+                                </xsl:attribute>
+                            </xsl:if>
+                            <xsl:if test="@table:number-rows-spanned">
+                                <xsl:attribute name="rowspan">
+                                    <xsl:value-of select="@table:number-rows-spanned"/>
+                                </xsl:attribute>
+                            </xsl:if>
+
+                            <xsl:attribute name="style">
+                                <!-- cell-style -->
+                                <xsl:value-of select="$collectedGlobalData/allstyles/*[name()=current()/@table:style-name]"/>
+                                <!-- column-style -->
+                                <xsl:value-of select="$allColumnStyleEntries/column-style-entry[position()=$column-position]"/>
+                                <!-- TABLE:VALUE-TYPE - the value of a table-cell will be aligned left by default only exlicit non-string is aligned:right-->
+                                <xsl:choose>
+                                    <xsl:when test="@table:value-type and not(@table:value-type = 'string')">
+                                        <xsl:text>text-align:right;</xsl:text>
+                                    </xsl:when>
+                                    <xsl:otherwise>
+                                        <xsl:text>text-align:left;</xsl:text>
+                                    </xsl:otherwise>
+                                </xsl:choose>
+                            </xsl:attribute>
+
+                            <!-- &#160 is a non-breakable space, necessary to make to the browser show the table-cell grid -->
+                            <xsl:if test="not(child::text()) and not(child::*)"> &#160;</xsl:if>
+                            <xsl:apply-templates>
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:apply-templates>
+                        </xsl:element>
+                    </xsl:when>
+                    <!--+++++ PALM INLINED WAY  +++++-->
+                    <xsl:when test="$outputType = 'PALM'">
+                        <xsl:element name="{$tableDataType}">
+                            <xsl:if test="@table:number-columns-spanned">
+                                <xsl:attribute name="colspan">
+                                    <xsl:value-of select="@table:number-columns-spanned"/>
+                                </xsl:attribute>
+                            </xsl:if>
+
+                            <xsl:if test="@table:number-rows-spanned">
+                                <xsl:attribute name="rowspan">
+                                    <xsl:value-of select="@table:number-rows-spanned"/>
+                                </xsl:attribute>
+                            </xsl:if>
+                            <xsl:apply-templates>
+                                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                            </xsl:apply-templates>
+                        </xsl:element>
+                    </xsl:when>
+                    <!--+++++ WML WAY  +++++-->
+                    <xsl:when test="$outputType = 'WML'">
+                        <xsl:choose>
+                            <xsl:when test="not($allColumnStyleEntries/column-style-entry[last() = $column-position])">
+                                <xsl:apply-templates>
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:apply-templates>
+                                <xsl:text>, </xsl:text>
+                            </xsl:when>
+                            <xsl:otherwise>
+                                <xsl:apply-templates>
+                                    <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+                                </xsl:apply-templates>
+                                <xsl:text>; </xsl:text>
+                                <xsl:element name="br"/>
+                            </xsl:otherwise>
+                        </xsl:choose>
+                    </xsl:when>
+                </xsl:choose>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/table_columns.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/table_columns.xsl
new file mode 100644 (file)
index 0000000..a9a907f
--- /dev/null
@@ -0,0 +1,215 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                xmlns:system="http://www.jclark.com/xt/java/java.lang.System"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+
+    <!-- ******************************************** -->
+    <!-- *** Create table columns style variable  *** -->
+    <!-- ******************************************** -->
+
+    <xsl:template name="adding-column-styles-entries">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="allTableColumns"/>
+
+        <xsl:for-each select="$allTableColumns">
+
+            <xsl:variable name="column-style-entry" select="$collectedGlobalData/allstyles/*[name() = translate(current()/@table:style-name, '. %()/\', '')]"/>
+            <xsl:choose>
+                <xsl:when test="not(@table:number-columns-repeated)">
+                    <!-- writes an entry of a column in the columns-variable -->
+                    <xsl:call-template name="adding-column-style-entry">
+                        <xsl:with-param name="column-style-entry" select="$column-style-entry"/>
+                    </xsl:call-template>
+                </xsl:when>
+                <!-- No higher repetition of cells greater than 4 for the last and second last column -->
+                <!-- a hack for the sample document 'Waehrungsumrechner.sxc having 230 repeated columns in the second last column -->
+                <!-- ??? <xsl:when test="(position() = last() or (position() = (last() - 1)) and @table:number-columns-repeated &lt; 5)"> ???-->
+                <xsl:when test="position() = last() or position() = (last() - 1)">
+                    <xsl:if test="@table:number-columns-repeated &lt; 5">
+                        <!-- writes an entry of a column in the columns-variable -->
+                        <xsl:call-template name="repeat-adding-column-style-entry">
+                            <xsl:with-param name="column-style-entry"       select="$column-style-entry"/>
+                            <xsl:with-param name="number-columns-repeated"  select="1"/>
+                        </xsl:call-template>
+                    </xsl:if>
+                </xsl:when>
+                <xsl:otherwise>
+                    <!-- repeated colums will be written explicit several times in the variable-->
+                    <xsl:call-template name="repeat-adding-column-style-entry">
+                        <xsl:with-param name="column-style-entry"           select="$column-style-entry"/>
+                        <xsl:with-param name="number-columns-repeated"      select="@table:number-columns-repeated"/>
+                    </xsl:call-template>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:for-each>
+     </xsl:template>
+
+
+    <!-- WRITES THE REPEATED COLUMN STYLE EXPLICIT AS AN ELEMENT IN THE COLUMNS-VARIABLE -->
+    <xsl:template name="repeat-adding-column-style-entry">
+        <xsl:param name="column-style-entry"/>
+        <xsl:param name="number-columns-repeated"/>
+
+        <xsl:choose>
+            <xsl:when test="$number-columns-repeated > 1">
+                <!-- writes an entry of a column in the columns-variable -->
+                <xsl:call-template name="adding-column-style-entry">
+                    <xsl:with-param name="column-style-entry"   select="$column-style-entry"/>
+                </xsl:call-template>
+                <!-- repeat calling this method until all elements written out -->
+                <xsl:call-template name="repeat-adding-column-style-entry">
+                    <xsl:with-param name="column-style-entry"       select="$column-style-entry"/>
+                    <xsl:with-param name="number-columns-repeated"  select="$number-columns-repeated - 1"/>
+                </xsl:call-template>
+            </xsl:when>
+            <xsl:otherwise>
+                <!-- writes an entry of a column in the columns-variable -->
+                <xsl:call-template name="adding-column-style-entry">
+                    <xsl:with-param name="column-style-entry"   select="$column-style-entry"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+    <!-- THE COLUMN-STYLE WRITE-PATTERN FOR EACH COLUMN WRITTEN IN A VARIABLE -->
+    <xsl:template name="adding-column-style-entry">
+        <xsl:param name="column-style-entry"/>
+
+        <xsl:element name="column-style-entry">
+            <xsl:choose>
+                <xsl:when test="@table:visibility = 'collapse' or @table:visibility = 'filter'">
+                    <xsl:attribute name="column-hidden-flag">true</xsl:attribute>
+                </xsl:when>
+                <xsl:otherwise>
+                    <xsl:variable  name="table:style-name"         select="translate(@table:style-name, '. %()/\', '')"/>
+                    <xsl:attribute name="style-name"><xsl:value-of select="$table:style-name"/></xsl:attribute>
+                    <xsl:value-of select="$column-style-entry"/>
+                </xsl:otherwise>
+            </xsl:choose>
+        </xsl:element>
+    </xsl:template>
+
+
+
+    <!--isDebugMode-START-->
+    <!-- giving out the 'allColumnStyle' variable:
+        For each 'column-style-entry' of the 'allColumnStyleEntries' variable the style-name is given out.
+        In case of 'column-hidden-flag' attribute the text 'Column is hidden is given out.-->
+    <xsl:template name="table-debug-allColumnStyleEntries">
+        <xsl:param name="allColumnStyleEntries"/>
+
+        <!-- debug output as table summary attribut in html -->
+        <xsl:attribute name="summary">
+            <xsl:call-template name="table-debug-column-out">
+                <xsl:with-param name="allColumnStyleEntries" select="$allColumnStyleEntries"/>
+            </xsl:call-template>
+        </xsl:attribute>
+        <!-- debug output to console -->
+        <xsl:message>
+            <xsl:call-template name="table-debug-column-out">
+                <xsl:with-param name="allColumnStyleEntries" select="$allColumnStyleEntries"/>
+            </xsl:call-template>
+        </xsl:message>
+    </xsl:template>
+
+
+    <xsl:template name="table-debug-column-out">
+        <xsl:param name="allColumnStyleEntries"/>
+            <xsl:text>
+            DebugInformation: For each 'column-style-entry' of the 'allColumnStyleEntries' variable the style-name is given out.
+                              In case of 'column-hidden-flag' attribute the text 'column is hidden' is given out.
+            </xsl:text>
+                <xsl:for-each select="$allColumnStyleEntries/column-style-entry">
+                <xsl:choose>
+                <xsl:when test="@column-hidden-flag">
+            <xsl:text>  </xsl:text><xsl:value-of select="@style-name"/><xsl:text>column is hidden</xsl:text><xsl:text>
+            </xsl:text>
+                </xsl:when>
+                <xsl:otherwise>
+            <xsl:text>  </xsl:text><xsl:value-of select="@style-name"/><xsl:text> = </xsl:text><xsl:value-of select="."/><xsl:text>
+            </xsl:text>
+                </xsl:otherwise>
+                </xsl:choose>
+                           </xsl:for-each>
+    </xsl:template>
+    <!--isDebugMode-END-->
+
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/ooo/table_rows.xsl b/framework/Mime/lib/Horde/Mime/Viewer/ooo/table_rows.xsl
new file mode 100644 (file)
index 0000000..6f7d17d
--- /dev/null
@@ -0,0 +1,177 @@
+<!--
+
+   The Contents of this file are made available subject to the terms of
+   either of the following licenses
+
+          - GNU Lesser General Public License Version 2.1
+          - Sun Industry Standards Source License Version 1.1
+
+   Sun Microsystems Inc., October, 2000
+
+   GNU Lesser General Public License Version 2.1
+   =============================================
+   Copyright 2000 by Sun Microsystems, Inc.
+   901 San Antonio Road, Palo Alto, CA 94303, USA
+
+   This library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License version 2.1, as published by the Free Software Foundation.
+
+   This library 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
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with this library; if not, write to the Free Software
+   Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+   MA  02111-1307  USA
+
+
+   Sun Industry Standards Source License Version 1.1
+   =================================================
+   The contents of this file are subject to the Sun Industry Standards
+   Source License Version 1.1 (the "License"); You may not use this file
+   except in compliance with the License. You may obtain a copy of the
+   License at http://www.openoffice.org/license.html.
+
+   Software provided under this License is provided on an "AS IS" basis,
+   WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING,
+   WITHOUT LIMITATION, WARRANTIES THAT THE SOFTWARE IS FREE OF DEFECTS,
+   MERCHANTABLE, FIT FOR A PARTICULAR PURPOSE, OR NON-INFRINGING.
+   See the License for the specific provisions governing your rights and
+   obligations concerning the Software.
+
+   The Initial Developer of the Original Code is: Sun Microsystems, Inc.
+
+   Copyright © 2002 by Sun Microsystems, Inc.
+
+   All Rights Reserved.
+
+   Contributor(s): _______________________________________
+
+-->
+<xsl:stylesheet version="1.0"
+                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+                xmlns:office="http://openoffice.org/2000/office"
+                xmlns:style="http://openoffice.org/2000/style"
+                xmlns:text="http://openoffice.org/2000/text"
+                xmlns:table="http://openoffice.org/2000/table"
+                xmlns:draw="http://openoffice.org/2000/drawing"
+                xmlns:fo="http://www.w3.org/1999/XSL/Format"
+                xmlns:xlink="http://www.w3.org/1999/xlink"
+                xmlns:number="http://openoffice.org/2000/datastyle"
+                xmlns:svg="http://www.w3.org/2000/svg"
+                xmlns:chart="http://openoffice.org/2000/chart"
+                xmlns:dr3d="http://openoffice.org/2000/dr3d"
+                xmlns:math="http://www.w3.org/1998/Math/MathML"
+                xmlns:form="http://openoffice.org/2000/form"
+                xmlns:script="http://openoffice.org/2000/script"
+                office:class="text"
+                office:version="1.0"
+                xmlns:dc="http://purl.org/dc/elements/1.1/"
+                xmlns:meta="http://openoffice.org/2000/meta"
+                xmlns:config="http://openoffice.org/2001/config"
+                xmlns:help="http://openoffice.org/2000/help"
+                xmlns:xt="http://www.jclark.com/xt"
+                xmlns:system="http://www.jclark.com/xt/java/java.lang.System"
+                xmlns:xalan="http://xml.apache.org/xalan"
+                xmlns:java="http://xml.apache.org/xslt/java"
+                exclude-result-prefixes="java">
+
+
+
+    <!-- ********************************************* -->
+    <!-- *** write (explicit) repeating table rows *** -->
+    <!-- ********************************************* -->
+
+    <xsl:template name="repeat-write-row">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="allColumnStyleEntries"/>
+        <xsl:param name="number-rows-repeated" select="1"/>
+        <xsl:param name="maxRowLength"/>
+
+        <xsl:choose>
+            <!-- write an entry of a row and repeat calling this method until all elements are written out -->
+            <xsl:when test="$number-rows-repeated > 1 and (table:table-cell/text() or table:table-cell/*)">
+                <xsl:call-template name="write-row">
+                    <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                    <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                    <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                </xsl:call-template>
+
+                <!-- 2DO: take variable from the output of repeated write-row and iterate giving out the variable -->
+                <xsl:call-template name="repeat-write-row">
+                    <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                    <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                    <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                    <xsl:with-param name="number-rows-repeated"     select="$number-rows-repeated - 1"/>
+                </xsl:call-template>
+            </xsl:when>
+            <!-- write a single entry of a row -->
+            <xsl:otherwise>
+                <xsl:call-template name="write-row">
+                    <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                    <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                    <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+                </xsl:call-template>
+            </xsl:otherwise>
+        </xsl:choose>
+    </xsl:template>
+
+
+
+    <xsl:template name="write-row">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="allColumnStyleEntries"/>
+        <xsl:param name="maxRowLength"/>
+
+
+        <xsl:element name="tr">
+            <!-- writing the style of the row -->
+            <xsl:call-template name='add-style-properties'>
+                <xsl:with-param name="collectedGlobalData" select="$collectedGlobalData"/>
+            </xsl:call-template>
+
+            <xsl:if test="$isDebugMode">
+                <xsl:message>
+*************************'tr' element has been added!</xsl:message>
+            </xsl:if>
+
+            <xsl:apply-templates select="table:table-cell">
+                <xsl:with-param name="collectedGlobalData"       select="$collectedGlobalData"/>
+                <xsl:with-param name="allColumnStyleEntries"    select="$allColumnStyleEntries"/>
+                <xsl:with-param name="maxRowLength"             select="$maxRowLength"/>
+            </xsl:apply-templates>
+
+        </xsl:element>
+    </xsl:template>
+
+
+    <!-- **************************** -->
+    <!-- *** HELPER: table styles *** -->
+    <!-- **************************** -->
+
+    <xsl:template name="add-style-properties">
+        <xsl:param name="collectedGlobalData"/>
+        <xsl:param name="allColumnStyleEntries"/>
+        <xsl:param name="node-position"/>
+
+        <xsl:choose>
+            <!--+++++ CSS (CASCADING STLYE SHEET) HEADER STYLE WAY +++++-->
+            <xsl:when test="$outputType = 'CSS_HEADER'">
+                <xsl:attribute name="class">
+                    <xsl:value-of select="translate(@table:style-name, '. %()/\', '')"/>
+                </xsl:attribute>
+            </xsl:when>
+
+            <!--+++++ HTML 4.0 INLINED WAY  +++++-->
+            <xsl:when test="$outputType = 'CSS_INLINED'">
+                <xsl:attribute name="style">
+                    <xsl:value-of select="$collectedGlobalData/allstyles/*[name()=current()/@table:style-name]"/>
+                </xsl:attribute>
+            </xsl:when>
+        </xsl:choose>
+    </xsl:template>
+
+</xsl:stylesheet>
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/pdf.php b/framework/Mime/lib/Horde/Mime/Viewer/pdf.php
new file mode 100644 (file)
index 0000000..9b2c1e0
--- /dev/null
@@ -0,0 +1,45 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_pdf class simply outputs the PDF file with the
+ * content-type 'application/pdf' enabling web browsers with a PDF viewer
+ * plugin to view the PDF file inside the browser.
+ *
+ * Copyright 2003-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_pdf extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_mimepart->getContents(),
+                'status' => array(),
+                'type' => 'application/pdf'
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/php.php b/framework/Mime/lib/Horde/Mime/Viewer/php.php
new file mode 100644 (file)
index 0000000..e8c0605
--- /dev/null
@@ -0,0 +1,157 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_php class renders out syntax-highlighted PHP code in
+ * HTML format.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_php extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $ret = $this->_renderInline();
+
+        // Need Horde headers for CSS tags.
+        reset($ret);
+        $ret[key($ret)]['data'] =  Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-header.inc') .
+            $ret[key($ret)]['data'] .
+            Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-footer.inc');
+
+        return $ret;
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        ini_set('highlight.comment', 'comment');
+        ini_set('highlight.default', 'default');
+        ini_set('highlight.keyword', 'keyword');
+        ini_set('highlight.string', 'string');
+        ini_set('highlight.html', 'html');
+
+        $code = $this->_mimepart->getContents();
+        $text = (strpos($code, '<?php') === false)
+            ? $this->_lineNumber(str_replace('&lt;?php&nbsp;', '', highlight_string('<?php ' . $code, true)))
+            : $this->_lineNumber(highlight_string($code, true));
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $text,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Add line numbers to a block of code.
+     *
+     * @param string $code  The code to number.
+     *
+     * @return string  The code with line numbers added.
+     */
+    protected function _lineNumber($code, $linebreak = "\n")
+    {
+        // Clean up.
+        $code = preg_replace(
+            array(
+                '/<code><span style="color: #?\w+">\s*/',
+                '/<code><font color="#?\w+">\s*/',
+                '/\s*<\/span>\s*<\/span>\s*<\/code>/',
+                '/\s*<\/font>\s*<\/font>\s*<\/code>/'
+            ), '', $code);
+
+        $code = str_replace(
+            array('&nbsp;', '&amp;', '<br />', '<span style="color: ', '<font color="', '</font>',),
+            array(' ', '&#38;', "\n", '<span class="', '<span class="', '</span>',),
+            $code);
+
+        $code = trim($code);
+
+        // Normalize newlines.
+        $code = str_replace("\r", '', $code);
+        $code = preg_replace('/\n\n\n+/', "\n\n", $code);
+
+        $results = array('<ol class="code-listing striped">');
+        $previous = false;
+
+        $lines = explode("\n", $code);
+        reset($lines);
+        while (list($lineno, $line) = each($lines)) {
+            if (substr($line, 0, 7) == '</span>') {
+                $previous = false;
+                $line = substr($line, 7);
+            }
+
+            if (empty($line)) {
+                $line = '&#160;';
+            }
+
+            if ($previous) {
+                $line = "<span class=\"$previous\">" . $line;
+            }
+
+            // Save the previous style.
+            if (strpos($line, '<span') !== false) {
+                switch (substr($line, strrpos($line, '<span') + 13, 1)) {
+                case 'c':
+                    $previous = 'comment';
+                    break;
+
+                case 'd':
+                    $previous = 'default';
+                    break;
+
+                case 'k':
+                    $previous = 'keyword';
+                    break;
+
+                case 's':
+                    $previous = 'string';
+                    break;
+                }
+            }
+
+            // Unset previous style unless the span continues.
+            if (substr($line, -7) == '</span>') {
+                $previous = false;
+            } elseif ($previous) {
+                $line .= '</span>';
+            }
+
+            $results[] = '<li id="l' . ($lineno + 1). '">' . $line . '</li>';
+        }
+
+        $results[] = '</ol>';
+
+        return implode("\n", $results);
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/plain.php b/framework/Mime/lib/Horde/Mime/Viewer/plain.php
new file mode 100644 (file)
index 0000000..132dd21
--- /dev/null
@@ -0,0 +1,96 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_plain class renders out plain text with URLs made
+ * into hyperlinks (if viewing inline).
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_plain extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $text = $this->_mimepart->getContents();
+        $charset = $this->_mimepart->getCharset();
+
+        /* Check for 'flowed' text data. */
+        if ($this->_mimepart->getContentTypeParameter('format') == 'flowed') {
+            $text = $this->_formatFlowed($text, $this->_mimepart->getContentTypeParameter('delsp'));
+        }
+
+        require_once 'Horde/Text/Filter.php';
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => '<html><body><tt>' . Text_Filter::filter($text, 'text2html', array('parselevel' => TEXT_HTML_MICRO, 'charset' => $charset, 'class' => null)) . '</tt></body></html>',
+                'status' => array(),
+                'type' => 'text/html; charset=' . $charset
+            )
+        );
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        $text = String::convertCharset($this->_mimepart->getContents(), $this->_mimepart->getCharset());
+
+        /* Check for 'flowed' text data. */
+        $data = ($this->_mimepart->getContentTypeParameter('format') == 'flowed')
+            ? $this->_formatFlowed($text, $this->_mimepart->getContentTypeParameter('delsp'))
+            : $text;
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $data,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Format flowed text for HTML output.
+     *
+     * @param string $text    The text to format.
+     * @param boolean $delsp  Was text created with DelSp formatting?
+     *
+     * @return string  The formatted text.
+     */
+    protected function _formatFlowed($text, $delsp = null)
+    {
+        require_once 'Text/Flowed.php';
+        $flowed = new Text_Flowed($this->_mimepart->replaceEOL($text, "\n"), $this->_mimepart->getCharset());
+        $flowed->setMaxLength(0);
+        if (!is_null($delsp)) {
+            $flowed->setDelSp($delsp);
+        }
+        return $flowed->toFixed();
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/rar.php b/framework/Mime/lib/Horde/Mime/Viewer/rar.php
new file mode 100644 (file)
index 0000000..aa2da6e
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_rar class renders out the contents of .rar archives
+ * in HTML format.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @author  Michael Cochrane <mike@graftonhall.co.nz>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_rar extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => true,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $ret = $this->_renderInline();
+        if (!empty($ret)) {
+            reset($ret);
+            $ret[key($ret)]['data'] = '<html><body>' . $ret[key($ret)]['data'] . '</body></html>';
+        }
+        return $ret;
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        $contents = $this->_mimepart->getContents();
+
+        require_once 'Horde/Compress.php';
+        $rar = &Horde_Compress::singleton('rar');
+
+        $rarData = $rar->decompress($contents);
+        if (is_a($rarData, 'PEAR_Error')) {
+            return array();
+        }
+        $fileCount = count($rarData);
+
+        require_once 'Horde/Text.php';
+
+        $name = $this->_mimepart->getName(true);
+        if (empty($name)) {
+            $name = _("unnamed");
+        }
+
+        $text = '<strong>' . htmlspecialchars(sprintf(_("Contents of \"%s\""), $name)) . ':</strong>' . "\n" .
+            '<table><tr><td align="left"><tt><span class="fixed">' .
+            Text::htmlAllSpaces(_("Archive Name") . ':  ' . $name) . "\n" .
+            Text::htmlAllSpaces(_("Archive File Size") . ': ' . strlen($contents) . ' bytes') . "\n" .
+            Text::htmlAllSpaces(sprintf(ngettext("File Count: %d file", "File Count: %d files", $fileCount), $fileCount)) .
+            "\n\n" .
+            Text::htmlAllSpaces(
+                String::pad(_("File Name"), 50, ' ', STR_PAD_RIGHT) .
+                String::pad(_("Attributes"), 10, ' ', STR_PAD_LEFT) .
+                String::pad(_("Size"), 10, ' ', STR_PAD_LEFT) .
+                String::pad(_("Modified Date"), 19, ' ', STR_PAD_LEFT) .
+                String::pad(_("Method"), 10, ' ', STR_PAD_LEFT) .
+                String::pad(_("Ratio"), 10, ' ', STR_PAD_LEFT)
+            ) . "\n" .
+            str_repeat('-', 109) . "\n";
+
+        foreach ($rarData as $val) {
+            $ratio = empty($val['size'])
+                ? 0
+                : 100 * ($val['csize'] / $val['size']);
+
+            $text .= Text::htmlAllSpaces(
+                String::pad($val['name'], 50, ' ', STR_PAD_RIGHT) .
+                String::pad($val['attr'], 10, ' ', STR_PAD_LEFT) .
+                String::pad($val['size'], 10, ' ', STR_PAD_LEFT) .
+                String::pad(strftime("%d-%b-%Y %H:%M", $val['date']), 19, ' ', STR_PAD_LEFT) .
+                String::pad($val['method'], 10, ' ', STR_PAD_LEFT) .
+                String::pad(sprintf("%1.1f%%", $ratio), 10, ' ', STR_PAD_LEFT)
+            ) . "\n";
+        }
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => nl2br($text . str_repeat('-', 106) . "\n" . '</span></tt></td></tr></table>'),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/report.php b/framework/Mime/lib/Horde/Mime/Viewer/report.php
new file mode 100644 (file)
index 0000000..6a0c5ab
--- /dev/null
@@ -0,0 +1,33 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_report class is a wrapper used to load the
+ * appropriate Horde_Mime_Viewer for multipart/report data (RFC 3462).
+ *
+ * Copyright 2003-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_report extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Return the underlying MIME Viewer for this part.
+     *
+     * @return mixed  A Horde_Mime_Viewer object, or false if not found.
+     */
+    protected function _getViewer()
+    {
+        if (!($type = $this->_mimepart->getContentTypeParameter('report-type'))) {
+            return false;
+        }
+
+        $viewer = Horde_Mime_Viewer::factory($this->_mimepart, 'message/' . String::lower($type));
+        if ($viewer) {
+            $viewer->setParams($this->_params);
+        }
+        return $viewer;
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/rfc822.php b/framework/Mime/lib/Horde/Mime/Viewer/rfc822.php
new file mode 100644 (file)
index 0000000..dee6487
--- /dev/null
@@ -0,0 +1,94 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_rfc822 class renders out messages from the
+ * message/rfc822 content type.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_rfc822 extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => true,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_mimepart->getContents(),
+                'status' => array(),
+                'type' => 'text/plain; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Return the rendered information about the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInfo()
+    {
+        /* Get the text of the part.  Since we need to look for the end of
+         * the headers by searching for the CRLFCRLF sequence, use
+         * getCanonicalContents() to make sure we are getting the text with
+         * CRLF's. */
+        $text = $this->_mimepart->getCanonicalContents();
+        if (empty($text)) {
+            return array();
+        }
+
+        /* Search for the end of the header text (CRLFCRLF). */
+        $text = substr($text, 0, strpos($text, "\r\n\r\n"));
+
+        /* Get the list of headers now. */
+        $headers = Horde_Mime_Headers::parseHeaders($text);
+
+        $header_array = array(
+            'date' => _("Date"),
+            'from' => _("From"),
+            'to' => _("To"),
+            'cc' => _("Cc"),
+            'bcc' => _("Bcc"),
+            'reply-to' => _("Reply-To"),
+            'subject' => _("Subject")
+        );
+        $header_output = array();
+
+        foreach ($header_array as $key => $val) {
+            $hdr = $headers->getValue($key);
+            if (!empty($hdr)) {
+                $header_output[] = '<strong>' . $val . ':</strong> ' . htmlspecialchars($hdr);
+            }
+        }
+
+        require_once 'Horde/Text/Filter.php';
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => empty($header_output) ? '' : ('<div class="mimeHeaders">' . Text_Filter::filter(implode("<br />\n", $header_output), 'emails') . '</div>'),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/richtext.php b/framework/Mime/lib/Horde/Mime/Viewer/richtext.php
new file mode 100644 (file)
index 0000000..8ce2b83
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_richtext class renders out HTML text from
+ * text/richtext content tags, (RFC 1896 [7.1.3]).
+ *
+ * A minimal richtext implementation is one that simply converts "<lt>" to
+ * "<", converts CRLFs to SPACE, converts <nl> to a newline according to
+ * local newline convention, removes everything between a <comment> command
+ * and the next balancing </comment> command, and removes all other
+ * formatting commands (all text enclosed in angle brackets).
+ *
+ * We implement the following tags:
+ *   <bold>, <italic>, <fixed>, <smaller>, <bigger>, <underline>, <center>,
+ *   <flushleft>, <flushright>, <indent>, <subscript>, <excerpt>, <paragraph>,
+ *   <signature>, <comment>, <no-op>, <lt>, <nl>
+ *
+ * The following tags are implemented differently than described in the RFC
+ * (due to limitations in HTML output):
+ *   <heading> - Output as centered, bold text.
+ *   <footing> - Output as centered, bold text.
+ *   <np>      - Output as paragraph break.
+ *
+ * The following tags are NOT implemented:
+ *   <indentright>, <outdent>, <outdentright>, <samepage>, <iso-8859-X>,
+ *   <us-ascii>,
+ *
+ * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_richtext extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_toHTML(),
+                'status' => array(),
+                'type' => 'text/html; charset=' . $this->_mimepart->getCharset()
+            )
+        );
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => String::convertCharset($this->_toHTML(), $this->_mimepart->getCharset()),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Convert richtext to HTML.
+     *
+     * @return string  The converted HTML string.
+     */
+    protected function _toHTML()
+    {
+        $text = trim($this->_mimepart->getContents());
+        if ($text == '') {
+            return array();
+        }
+
+        /* We add space at the beginning and end of the string as it will
+         * make some regular expression checks later much easier (so we
+         * don't have to worry about start/end of line characters). */
+        $text = ' ' . $text . ' ';
+
+        /* Remove everything between <comment> tags. */
+        $text = preg_replace('/<comment.*>.*<\/comment>/Uis', '', $text);
+
+        /* Remove any unrecognized tags in the text.  We don't need <no-op>
+         * in $tags since it doesn't do anything anyway.  All <comment> tags
+         * have already been removed. */
+        $tags = '<bold><italic><fixed><smaller><bigger><underline><center><flushleft><flushright><indent><subscript><excerpt><paragraph><signature><lt><nl>';
+        $text = strip_tags($text, $tags);
+
+        /* <lt> becomes a '<'. CRLF becomes a SPACE. */
+        $text = str_ireplace(array('<lt>', "\r\n"), array('&lt;', ' '), $text);
+
+        /* We try to protect against bad stuff here. */
+        $text = @htmlspecialchars($text, ENT_QUOTES, $this->_mimepart->getCharset());
+
+        /* <nl> becomes a newline (<br />);
+         * <np> becomes a paragraph break (<p />). */
+        $text = str_ireplace(array('&lt;nl&gt;', '&lt;np&gt;'), array('<br />', '<p />'), $text);
+
+        /* Now convert the known tags to html. Try to remove any tag
+         * parameters to stop people from trying to pull a fast one. */
+        $replace = array(
+            '/(?<!&lt;)&lt;bold.*&gt;(.*)&lt;\/bold&gt;/Uis' => '<span style="font-weight: bold">\1</span>',
+            '/(?<!&lt;)&lt;italic.*&gt;(.*)&lt;\/italic&gt;/Uis' => '<span style="font-style: italic">\1</span>',
+            '/(?<!&lt;)&lt;fixed.*&gt;(.*)&lt;\/fixed&gt;/Uis' => '<font face="fixed">\1</font>',
+            '/(?<!&lt;)&lt;smaller.*&gt;(.*)&lt;\/smaller&gt;/Uis' => '<span style="font-size: smaller">\1</span>',
+            '/(?<!&lt;)&lt;bigger.*&gt;(.*)&lt;\/bigger&gt;/Uis' => '<span style="font-size: larger">\1</span>',
+            '/(?<!&lt;)&lt;underline.*&gt;(.*)&lt;\/underline&gt;/Uis' => '<span style="text-decoration: underline">\1</span>',
+            '/(?<!&lt;)&lt;center.*&gt;(.*)&lt;\/center&gt;/Uis' => '<div align="center">\1</div>',
+            '/(?<!&lt;)&lt;flushleft.*&gt;(.*)&lt;\/flushleft&gt;/Uis' => '<div align="left">\1</div>',
+            '/(?<!&lt;)&lt;flushright.*&gt;(.*)&lt;\/flushright&gt;/Uis' => '<div align="right">\1</div>',
+            '/(?<!&lt;)&lt;indent.*&gt;(.*)&lt;\/indent&gt;/Uis' => '<blockquote>\1</blockquote>',
+            '/(?<!&lt;)&lt;excerpt.*&gt;(.*)&lt;\/excerpt&gt;/Uis' => '<cite>\1</cite>',
+            '/(?<!&lt;)&lt;subscript.*&gt;(.*)&lt;\/subscript&gt;/Uis' => '<sub>\1</sub>',
+            '/(?<!&lt;)&lt;superscript.*&gt;(.*)&lt;\/superscript&gt;/Uis' => '<sup>\1</sup>',
+            '/(?<!&lt;)&lt;heading.*&gt;(.*)&lt;\/heading&gt;/Uis' => '<br /><div align="center" style="font-weight: bold">\1</div><br />',
+            '/(?<!&lt;)&lt;footing.*&gt;(.*)&lt;\/footing&gt;/Uis' => '<br /><div align="center" style="font-weight: bold">\1</div><br />',
+            '/(?<!&lt;)&lt;paragraph.*&gt;(.*)&lt;\/paragraph&gt;/Uis' => '<p>\1</p>',
+            '/(?<!&lt;)&lt;signature.*&gt;(.*)&lt;\/signature&gt;/Uis' => '<address>\1</address>'
+        );
+        $text = preg_replace(array_keys($replace), array_values($replace), $text);
+
+        /* Now we remove the leading/trailing space we added at the start. */
+        $text = substr($text, 1, -1);
+
+        /* Wordwrap. */
+        $text = str_replace(array("\t", '  ', "\n "), array('        ', ' &nbsp;', "\n&nbsp;"), $text);
+        if ($text[0] == ' ') {
+            $text = '&nbsp;' . substr($text, 1);
+        }
+
+        return '<p style="font-size:100%;font-family:Lucida Console,Courier,Courier New;">' . nl2br($text) . '</p>';
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/rpm.php b/framework/Mime/lib/Horde/Mime/Viewer/rpm.php
new file mode 100644 (file)
index 0000000..5d20cc3
--- /dev/null
@@ -0,0 +1,61 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_rpm class renders out lists of files in RPM
+ * packages by using the rpm tool to query the package.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_rpm extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => true,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        $data = '';
+        $tmp_rpm = Horde::getTempFile('horde_rpm');
+
+        file_put_contents($tmp_rpm, $this->_mimepart->getContents());
+
+        $fh = popen($this->_conf['location'] . " -qip $tmp_rpm 2>&1", 'r');
+        while (($rc = fgets($fh, 8192))) {
+            $data .= $rc;
+        }
+        pclose($fh);
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => '<html><body><pre>' . htmlentities($data) . '</pre></body></html>',
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/rtf.php b/framework/Mime/lib/Horde/Mime/Viewer/rtf.php
new file mode 100644 (file)
index 0000000..7750322
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_rtf class renders out Rich Text Format documents in
+ * HTML format by using the UnRTF package
+ * (http://www.gnu.org/software/unrtf/unrtf.html).
+ *
+ * Copyright 2007 Duck <duck@obala.net>
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Duck <duck@obala.net>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_rtf extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        $tmp_rtf = Horde::getTempFile('rtf');
+        $tmp_output = Horde::getTempFile('rtf');
+
+        file_put_contents($tmp_rtf, $this->_mimepart->getContents());
+
+        exec($GLOBALS['mime_drivers']['horde']['rtf']['location'] . " $tmp_rtf > $tmp_output");
+
+        if (file_exists($tmp_output)) {
+            $data = file_get_contents($tmp_output);
+        } else {
+            $data = _("Unable to translate this RTF document");
+        }
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $data,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/security.php b/framework/Mime/lib/Horde/Mime/Viewer/security.php
new file mode 100644 (file)
index 0000000..f5fd7f3
--- /dev/null
@@ -0,0 +1,34 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_security class is a wrapper used to load the
+ * appropriate Horde_Mime_Viewer for secure multipart messages (defined by RFC
+ * 1847). This class handles multipart/signed and multipart/encrypted data.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_security extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Return the underlying MIME Viewer for this part.
+     *
+     * @return mixed  A Horde_Mime_Viewer object, or false if not found.
+     */
+    protected function _getViewer()
+    {
+        if (!($protocol = $this->_mimepart->getContentTypeParameter('protocol'))) {
+            return false;
+        }
+
+        $viewer = Horde_Mime_Viewer::factory($this->_mimepart, $protocol);
+        if ($viewer) {
+            $viewer->setParams($this->_params);
+        }
+        return $viewer;
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/simple.php b/framework/Mime/lib/Horde/Mime/Viewer/simple.php
new file mode 100644 (file)
index 0000000..561ebd5
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_simple class renders out plain text without any
+ * modifications.
+ *
+ * Copyright 2004-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_simple extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_mimepart->getContents(),
+                'status' => array(),
+                'type' => 'text/plain; charset=' . $this->_mimepart->getCharset()
+            )
+        );
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => String::convertCharset($this->_mimepart->getContents(), $this->_mimepart->getCharset()),
+                'status' => array(),
+                'type' => 'text/plain; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/smil.php b/framework/Mime/lib/Horde/Mime/Viewer/smil.php
new file mode 100644 (file)
index 0000000..d8f0fe7
--- /dev/null
@@ -0,0 +1,109 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_smil renders SMIL documents to very basic HTML.
+ *
+ * Copyright 2006-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_smil extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Handle for the XML parser object.
+     *
+     * @var resource
+     */
+    protected $_parser;
+
+    /**
+     * String buffer to hold the generated content
+     *
+     * @var string
+     */
+    protected $_content;
+
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => true,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $this->_content = '';
+
+        /* Create a new parser and set its default properties. */
+        $this->_parser = xml_parser_create();
+        xml_set_object($this->_parser, $this);
+        xml_set_element_handler($this->_parser, '_startElement', '_endElement');
+        xml_set_character_data_handler($this->_parser, '_defaultHandler');
+        xml_parse($this->_parser, $this->_mimepart->getContents(), true);
+        xml_parser_free($this->_parser);
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_content,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * User-defined function callback for start elements.
+     *
+     * @param object $parser  Handle to the parser instance.
+     * @param string $name    The name of this XML element.
+     * @param array $attrs    List of this element's attributes.
+     */
+    protected function _startElement($parser, $name, $attrs)
+    {
+        switch ($name) {
+        case 'IMG':
+            if (isset($attrs['SRC'])) {
+                $this->_content .= '<img src="' . htmlspecialchars($attrs['SRC']) . '" />';
+            }
+            break;
+        }
+    }
+
+    /**
+     * User-defined function callback for end elements.
+     *
+     * @param object $parser  Handle to the parser instance.
+     * @param string $name    The name of this XML element.
+     */
+    protected function _endElement($parser, $name)
+    {
+    }
+
+    /**
+     * User-defined function callback for character data.
+     *
+     * @param object $parser  Handle to the parser instance.
+     * @param string $data    String of character data.
+     */
+    protected function _defaultHandler($parser, $data)
+    {
+        $data = trim($data);
+        if (!empty($data)) {
+            $this->_content .= ' ' . htmlspecialchars($data);
+        }
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/source.php b/framework/Mime/lib/Horde/Mime/Viewer/source.php
new file mode 100644 (file)
index 0000000..553640b
--- /dev/null
@@ -0,0 +1,31 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_source class is a class for any viewer that wants
+ * to provide line numbers to extend.
+ *
+ * Copyright 1999-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_source extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Add line numbers to a block of code.
+     *
+     * @param string $code  The code to number.
+     *
+     * @return string  The code with line numbers added.
+     */
+    protected function _lineNumber($code, $linebreak = "\n")
+    {
+        $html = array('<table class="lineNumbered" cellspacing="0"><tr><th>');
+        for ($l = 1,  $lines = substr_count($code, $linebreak) + 1; $l <= $lines; ++$l) {
+            $html[] = sprintf('<a id="l%s" href="#l%s">%s</a><br />', $l, $l, $l);
+        }
+        return implode("\n", $html) . '</th><td><div>' . $code . '</div></td></tr></table>';
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/srchighlite.php b/framework/Mime/lib/Horde/Mime/Viewer/srchighlite.php
new file mode 100644 (file)
index 0000000..44da544
--- /dev/null
@@ -0,0 +1,125 @@
+<?php
+
+require_once dirname(__FILE__) . '/source.php';
+
+/**
+ * The Horde_Mime_Viewer_srchighlite class renders out various content in HTML
+ * format by using Source-highlight.
+ *
+ * Source-highlight: http://www.gnu.org/software/src-highlite/
+ *
+ * Copyright 2003-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Mike Cochrane <mike@graftonhall.co.nz>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_srchighlite extends Horde_Mime_Viewer_source
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $ret = $this->_renderInline();
+
+        // Need Horde headers for CSS tags.
+        reset($ret);
+        $ret[key($ret)]['data'] =  Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-header.inc') .
+            $ret[key($ret)]['data'] .
+            Util::bufferOutput('require', $GLOBALS['registry']->get('templates', 'horde') . '/common-footer.inc');
+
+        return $ret;
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        /* Create temporary files for Webcpp. */
+        $tmpin  = Horde::getTempFile('SrcIn');
+        $tmpout = Horde::getTempFile('SrcOut', false);
+
+        /* Write the contents of our buffer to the temporary input file. */
+        file_put_contents($tmpin, $this->_mimepart->getContents());
+
+        /* Determine the language from the mime type. */
+        $lang = $this->_typeToLang($this->_mimepart->getType());
+
+        /* Execute Source-Highlite. */
+        exec($this->_conf['location'] . " --src-lang $lang --out-format xhtml --input $tmpin --output $tmpout");
+        $results = file_get_contents($tmpout);
+        unlink($tmpout);
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $this->_lineNumber($results),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Attempts to determine what mode to use for the source-highlight
+     * program from a MIME type.
+     *
+     * @param string $type  The MIME type.
+     *
+     * @return string  The mode to use.
+     */
+    protected function _typeToLang($type)
+    {
+        switch ($type) {
+        case 'text/x-java':
+            return 'java';
+
+        case 'text/x-csrc':
+        case 'text/x-c++src':
+        case 'text/cpp':
+            return 'cpp';
+
+        case 'application/x-perl':
+            return 'perl';
+
+        case 'application/x-php':
+        case 'x-extension/phps':
+        case 'x-extension/php3s':
+        case 'application/x-httpd-php':
+        case 'application/x-httpd-php3':
+        case 'application/x-httpd-phps':
+            return 'php3';
+
+        case 'application/x-python':
+            return 'python';
+
+        // TODO: 'prolog', 'flex', 'changelog', 'ruby'
+        }
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/tgz.php b/framework/Mime/lib/Horde/Mime/Viewer/tgz.php
new file mode 100644 (file)
index 0000000..d128227
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_tgz class renders out plain or gzipped tarballs in
+ * HTML.
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Anil Madhavapeddy <anil@recoil.org>
+ * @author  Michael Cochrane <mike@graftonhall.co.nz>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_tgz extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => true,
+        'full' => false,
+        'info' => true,
+        'inline' => true
+    );
+
+    /**
+     * The list of compressed subtypes.
+     *
+     * @var array
+     */
+    protected $_gzipSubtypes = array(
+        'x-compressed-tar', 'tgz', 'x-tgz', 'gzip', 'x-gzip',
+        'x-gzip-compressed', 'x-gtar'
+    );
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        /* Currently, can't do anything without tar file. */
+        $subtype = $this->_mimepart->getSubType();
+        if (in_array($subtype, array('gzip', 'x-gzip', 'x-gzip-compressed'))) {
+            return array();
+        }
+
+        $contents = $this->_mimepart->getContents();
+
+        /* Decompress gzipped files. */
+        if (in_array($subtype, $this->_gzipSubtypes)) {
+            $gzip = &Horde_Compress::singleton('gzip');
+            $contents = $gzip->decompress($contents);
+            if (is_a($contents, 'PEAR_Error') || empty($contents)) {
+                return array();
+            }
+        }
+
+        /* Obtain the list of files/data in the tar file. */
+        $tar = &Horde_Compress::singleton('tar');
+        $tarData = $tar->decompress($contents);
+        if (is_a($tarData, 'PEAR_Error')) {
+            return array();
+        }
+        $fileCount = count($tarData);
+
+        require_once 'Horde/Text.php';
+
+        $name = $this->_mimepart->getName(true);
+        if (empty($name)) {
+            $name = _("unnamed");
+        }
+
+        $text = '<strong>' . htmlspecialchars(sprintf(_("Contents of \"%s\""), $name)) . ':</strong>' . "\n" .
+            '<table><tr><td align="left"><tt><span class="fixed">' .
+            Text::htmlAllSpaces(_("Archive Name") . ':  ' . $name) . "\n" .
+            Text::htmlAllSpaces(_("Archive File Size") . ': ' . strlen($contents) . ' bytes') . "\n" .
+            Text::htmlAllSpaces(sprintf(ngettext("File Count: %d file", "File Count: %d files", $fileCount), $fileCount)) .
+            "\n\n" .
+            Text::htmlAllSpaces(
+                str_pad(_("File Name"), 62, ' ', STR_PAD_RIGHT) .
+                str_pad(_("Attributes"), 15, ' ', STR_PAD_LEFT) .
+                str_pad(_("Size"), 10, ' ', STR_PAD_LEFT) .
+                str_pad(_("Modified Date"), 19, ' ', STR_PAD_LEFT)
+            ) . "\n" .
+            str_repeat('-', 106) . "\n";
+
+        foreach ($tarData as $val) {
+            $text .= Text::htmlAllSpaces(
+                str_pad($val['name'], 62, ' ', STR_PAD_RIGHT) .
+                str_pad($val['attr'], 15, ' ', STR_PAD_LEFT) .
+                str_pad($val['size'], 10, ' ', STR_PAD_LEFT) .
+                str_pad(strftime("%d-%b-%Y %H:%M", $val['date']), 19, ' ', STR_PAD_LEFT)
+            ) . "\n";
+        }
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => nl2br($text . str_repeat('-', 106) . "\n" . '</span></tt></td></tr></table>'),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * Return the rendered information about the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInfo()
+    {
+        return $this->_renderInline();
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/tnef.php b/framework/Mime/lib/Horde/Mime/Viewer/tnef.php
new file mode 100644 (file)
index 0000000..5ea6413
--- /dev/null
@@ -0,0 +1,75 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_tnef class allows MS-TNEF attachments to be
+ * displayed.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Jan Schneider <jan@horde.org>
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_tnef extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => true,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $ret = $this->_renderInline();
+        if (!empty($ret)) {
+            reset($ret);
+            $ret[key($ret)]['data'] = '<html><body>' . $ret[key($ret)]['data'] . '</body></html>';
+        }
+        return $ret;
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        require_once 'Horde/Compress.php';
+        $tnef = &Horde_Compress::singleton('tnef');
+
+        $data = '<table border="1">';
+        $info = $tnef->decompress($this->_mimepart->getContents());
+        if (empty($info) || is_a($info, 'PEAR_Error')) {
+            $data .= '<tr><td>' . _("MS-TNEF Attachment contained no data.") . '</td></tr>';
+        } else {
+            $data .= '<tr><td>' . _("Name") . '</td><td>' . _("Mime Type") . '</td></tr>';
+            foreach ($info as $part) {
+                $data .= '<tr><td>' . $part['name'] . '</td><td>' . $part['type'] . '/' . $part['subtype'] . '</td></tr>';
+            }
+        }
+        $data .= '</table>';
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $data,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/vcard.php b/framework/Mime/lib/Horde/Mime/Viewer/vcard.php
new file mode 100644 (file)
index 0000000..1b2837e
--- /dev/null
@@ -0,0 +1,400 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_vcard class renders out vCards in HTML format.
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Jan Schneider <jan@horde.org>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_vcard extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $ret = $this->_renderInline();
+        if (!empty($ret)) {
+            reset($ret);
+            $ret[key($ret)]['data'] = Util::bufferOutput('include', $GLOBALS['registry']->get('templates', 'horde') . '/common-header.inc') .
+                $ret[key($ret)]['data'] .
+                Util::bufferOutput('include', $GLOBALS['registry']->get('templates', 'horde') . '/common-footer.inc');
+        }
+        return $ret;
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        global $registry, $prefs, $notification;
+
+        $app = false;
+        $data = $this->_mimepart->getContents();
+        $html = '';
+        $import_msg = null;
+        $title = _("vCard");
+
+        require_once 'Horde/iCalendar.php';
+        $iCal = new Horde_iCalendar();
+        if (!$iCal->parsevCalendar($data, 'VCALENDAR', $this->_mimepart->getCharset())) {
+            $notification->push(_("There was an error reading the contact data."), 'horde.error');
+        }
+
+        if (Util::getFormData('import') &&
+            Util::getFormData('source') &&
+            $registry->hasMethod('contacts/import')) {
+            $source = Util::getFormData('source');
+            $contacts = $registry->call('contacts/import',
+                                        array($data, 'text/x-vcard', $source));
+            if (is_a($contacts, 'PEAR_Error')) {
+                $notification->push(
+                    _("There was an error importing the contact data:") . ' '
+                    . $contacts->getMessage(),
+                    'horde.error');
+            } else {
+                $notification->push(sprintf(ngettext(
+                    "%d contact was successfully added to your address book.",
+                    "%d contacts were successfully added to your address book.",
+                    $iCal->getComponentCount()),
+                                            $iCal->getComponentCount()),
+                                    'horde.success');
+            }
+        }
+
+        $html .= '<table cellspacing="1" border="0" cellpadding="1">';
+
+        $i = 0;
+        foreach ($iCal->getComponents() as $vc) {
+            if ($i > 0) {
+                $html .= '<tr><td colspan="2">&nbsp;</td></tr>';
+            }
+            ++$i;
+
+            $html .= '<tr><td colspan="2" class="header">';
+            $fullname = $vc->getAttributeDefault('FN', false);
+            if ($fullname !== false) {
+                $html .= $fullname;
+            }
+            $html .= '</td></tr>';
+
+            $n = $vc->printableName();
+            if (!empty($n)) {
+                $html .= $this->_row(_("Name"), $n);
+            }
+
+            $aliases = $vc->getAttributeValues('ALIAS');
+            if (!is_a($aliases, 'PEAR_Error')) {
+                $html .= $this->_row(_("Alias"), implode("\n", $aliases));
+            }
+            $birthdays = $vc->getAttributeValues('BDAY');
+            if (!is_a($birthdays, 'PEAR_Error')) {
+                $birthday = new Horde_Date($birthdays[0]);
+                $html .= $this->_row(
+                    _("Birthday"),
+                    $birthday->strftime($prefs->getValue('date_format')));
+            }
+
+            $photos = $vc->getAllAttributes('PHOTO');
+            foreach ($photos as $photo) {
+                if (!isset($photo['params']['VALUE']) ||
+                    String::upper($photo['params']['VALUE']) != 'URI') {
+                    continue;
+                }
+                $html .= $this->_row(_("Photo"),
+                                     '<img src="' . htmlspecialchars($photo['value']) . '" />',
+                                     false);
+            }
+
+            $labels = $vc->getAllAttributes('LABEL');
+            foreach ($labels as $label) {
+                if (isset($label['params']['TYPE'])) {
+                    if (!is_array($item['params']['TYPE'])) {
+                        $item['params']['TYPE'] = array($item['params']['TYPE']);
+                    }
+                } else {
+                    $item['params']['TYPE'] = array_keys($item['params']);
+                }
+                $types = array();
+                foreach ($item['params']['TYPE'] as $type) {
+                    switch(String::upper($type)) {
+                    case 'HOME':
+                        $types[] = _("Home Address");
+                        break;
+
+                    case 'WORK':
+                        $types[] = _("Work Address");
+                        break;
+
+                    case 'DOM':
+                        $types[] = _("Domestic Address");
+                        break;
+
+                    case 'INTL':
+                        $types[] = _("International Address");
+                        break;
+
+                    case 'POSTAL':
+                        $types[] = _("Postal Address");
+                        break;
+
+                    case 'PARCEL':
+                        $types[] = _("Parcel Address");
+                        break;
+
+                    case 'PREF':
+                        $types[] = _("Preferred Address");
+                        break;
+
+                    default:
+                        $types[] = _("Address");
+                        break;
+                    }
+                }
+                $html .= $this->_row(implode('/', $types), $label['value']);
+            }
+
+            $adrs = $vc->getAllAttributes('ADR');
+            foreach ($adrs as $item) {
+                if (isset($item['params']['TYPE'])) {
+                    if (!is_array($item['params']['TYPE'])) {
+                        $item['params']['TYPE'] = array($item['params']['TYPE']);
+                    }
+                } else {
+                    $item['params']['TYPE'] = array_keys($item['params']);
+                }
+                $address = $item['values'];
+                $a = array();
+                if (isset($address[VCARD_ADR_STREET])) {
+                    $a[] = $address[VCARD_ADR_STREET];
+                }
+                if (isset($address[VCARD_ADR_LOCALITY])) {
+                    $a[] = $address[VCARD_ADR_LOCALITY];
+                }
+                if (isset($address[VCARD_ADR_REGION])) {
+                    $a[] = $address[VCARD_ADR_REGION];
+                }
+                if (isset($address[VCARD_ADR_POSTCODE])) {
+                    $a[] = $address[VCARD_ADR_POSTCODE];
+                }
+                if (isset($address[VCARD_ADR_COUNTRY])) {
+                    $a[] = $address[VCARD_ADR_COUNTRY];
+                }
+                $types = array();
+                foreach ($item['params']['TYPE'] as $type) {
+                    switch(String::upper($type)) {
+                    case 'HOME':
+                        $types[] = _("Home Address");
+                        break;
+
+                    case 'WORK':
+                        $types[] = _("Work Address");
+                        break;
+
+                    case 'DOM':
+                        $types[] = _("Domestic Address");
+                        break;
+
+                    case 'INTL':
+                        $types[] = _("International Address");
+                        break;
+
+                    case 'POSTAL':
+                        $types[] = _("Postal Address");
+                        break;
+
+                    case 'PARCEL':
+                        $types[] = _("Parcel Address");
+                        break;
+
+                    case 'PREF':
+                        $types[] = _("Preferred Address");
+                        break;
+
+                    default:
+                        $types[] = _("Address");
+                        break;
+                    }
+                }
+                $html .= $this->_row(implode('/', $types), implode("\n", $a));
+            }
+
+            $numbers = $vc->getAllAttributes('TEL');
+
+            foreach ($numbers as $number) {
+                if (isset($number['params']['TYPE'])) {
+                    if (!is_array($number['params']['TYPE'])) {
+                        $number['params']['TYPE'] = array($number['params']['TYPE']);
+                    }
+                    foreach ($number['params']['TYPE'] as $type) {
+                        $number['params'][String::upper($type)] = true;
+                    }
+                }
+                if (isset($number['params']['FAX'])) {
+                    $html .= $this->_row(_("Fax"), $number['value']);
+                } else {
+                    if (isset($number['params']['HOME'])) {
+                        $html .= $this->_row(_("Home Phone"),
+                                             $number['value']);
+                    } elseif (isset($number['params']['WORK'])) {
+                        $html .= $this->_row(_("Work Phone"),
+                                             $number['value']);
+                    } elseif (isset($number['params']['CELL'])) {
+                        $html .= $this->_row(_("Cell Phone"),
+                                             $number['value']);
+                    } else {
+                        $html .= $this->_row(_("Phone"),
+                                             $number['value']);
+                    }
+                }
+            }
+
+            $addresses = $vc->getAllAttributes('EMAIL');
+            $emails = array();
+            foreach ($addresses as $address) {
+                if (isset($address['params']['TYPE'])) {
+                    if (!is_array($address['params']['TYPE'])) {
+                        $address['params']['TYPE'] = array($address['params']['TYPE']);
+                    }
+                    foreach ($address['params']['TYPE'] as $type) {
+                        $address['params'][String::upper($type)] = true;
+                    }
+                }
+                $email = '<a href="';
+                if ($registry->hasMethod('mail/compose')) {
+                    $email .= $registry->call(
+                        'mail/compose',
+                        array(array('to' => $address['value'])));
+                } else {
+                    $email .= 'mailto:' . htmlspecialchars($address['value']);
+                }
+                $email .= '">' . htmlspecialchars($address['value']) . '</a>';
+                if (isset($address['params']['PREF'])) {
+                    array_unshift($emails, $email);
+                } else {
+                    $emails[] = $email;
+                }
+            }
+
+            if (count($emails)) {
+                $html .= $this->_row(_("Email"), implode("\n", $emails), false);
+            }
+
+            $title = $vc->getAttributeValues('TITLE');
+            if (!is_a($title, 'PEAR_Error')) {
+                $html .= $this->_row(_("Title"), $title[0]);
+            }
+
+            $role = $vc->getAttributeValues('ROLE');
+            if (!is_a($role, 'PEAR_Error')) {
+                $html .= $this->_row(_("Role"), $role[0]);
+            }
+
+            $org = $vc->getAttributeValues('ORG');
+            if (!is_a($org, 'PEAR_Error')) {
+                $html .= $this->_row(_("Company"), $org[0]);
+                if (isset($org[1])) {
+                    $html .= $this->_row(_("Department"), $org[1]);
+                }
+            }
+
+            $notes = $vc->getAttributeValues('NOTE');
+            if (!is_a($notes, 'PEAR_Error')) {
+                $html .= $this->_row(_("Notes"), $notes[0]);
+            }
+
+            $url = $vc->getAttributeValues('URL');
+            if (!is_a($url, 'PEAR_Error')) {
+                $html .= $this->_row(
+                    _("URL"),
+                    '<a href="' . htmlspecialchars($url[0])
+                        . '" target="_blank">' . htmlspecialchars($url[0])
+                        . '</a>',
+                    false);
+            }
+        }
+
+        if ($registry->hasMethod('contacts/import') &&
+            $registry->hasMethod('contacts/sources')) {
+            $html .= '<tr><td colspan="2" class="smallheader"><form action="'
+                . Horde::selfUrl() . '" method="get" name="vcard_import">'
+                . Util::formInput();
+            foreach ($_GET as $key => $val) {
+                $html .= '<input type="hidden" name="' . htmlspecialchars($key)
+                    . '" value="' . htmlspecialchars($val) . '" />';
+            }
+
+            $sources = $registry->call('contacts/sources', array(true));
+            if (count($sources) > 1) {
+                $html .=
+                    '<input type="submit" class="button" name="import" value="'
+                    . _("Add to address book:") . '" />'
+                    . '<label for="add_source" class="hidden">'
+                    . _("Address Book") . '</label>'
+                    . '<select id="add_source" name="source">';
+                foreach ($sources as $key => $label) {
+                    $selected = ($key == $prefs->getValue('add_source'))
+                        ? ' selected="selected"' : '';
+                    $html .= '<option value="' . htmlspecialchars($key) . '"'
+                        . $selected . '>' . htmlspecialchars($label)
+                        . '</option>';
+                }
+            } else {
+                reset($sources);
+                $html .=
+                    '<input type="submit" class="button" name="import" value="'
+                    . _("Add to my address book") . '" />'
+                    . '<input type="hidden" name="source" value="'
+                    . htmlspecialchars(key($sources)) . '" />';
+            }
+
+            $html .= '</form></td></tr><tr><td>&nbsp;</td></tr>';
+        }
+
+        $html .=  '</table>';
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => Util::bufferOutput(array($notification, 'notify'), array('listeners' => 'status')) . $html,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+
+    /**
+     * TODO
+     */
+    protected function _row($label, $value, $encode = true)
+    {
+        if ($encode) {
+            $label = htmlspecialchars($label);
+            $value = htmlspecialchars($value);
+        }
+        return '<tr><td class="item" valign="top">' . $label .
+            '</td><td class="item" valign="top">' . nl2br($value) .
+            "</td></tr>\n";
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/webcpp.php b/framework/Mime/lib/Horde/Mime/Viewer/webcpp.php
new file mode 100644 (file)
index 0000000..c0e4f41
--- /dev/null
@@ -0,0 +1,111 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_webcpp class renders out various content in HTML
+ * format by using Web C Plus Plus.
+ *
+ * Web C Plus plus: http://webcpp.sourceforge.net/
+ *
+ * Copyright 2002-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Mike Cochrane <mike@graftonhall.co.nz>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_webcpp extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $ret = $this->_toHTML();
+
+        /* The first 2 lines are the Content-Type line and a blank line so
+         * remove them before outputting. */
+        reset($ret);
+        $ret[key($ret)]['data'] = preg_replace("/.*\n.*\n/", '', $ret[key($ret)]['data'], 1);
+
+        return $ret;
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        $ret = $this->_toHTML();
+        reset($ret);
+        $data = $ret[key($ret)]['data'];
+
+        /* Extract the style sheet, removing any global body formatting
+         * if we're displaying inline. */
+        $res = preg_split(';(</style>)|(<style type="text/css">);', $data);
+        $style = $res[1];
+        $style = preg_replace('/\nbody\s+?{.*?}/s', '', $style);
+
+        /* Extract the content. */
+        $res = preg_split('/\<\/?pre\>/', $data);
+        $body = $res[1];
+
+        $ret[key($ret)]['data'] = '<style>' . $style . '</style><div class="webcpp" style="white-space:pre;font-family:Lucida Console,Courier,monospace;">' . $body . '</div>';
+
+        return $ret;
+    }
+
+    /**
+     * Converts the code to HTML.
+     *
+     * @return string  The HTML-ified version of the MIME part contents.
+     */
+    protected function _toHTML()
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        /* Create temporary files for Webcpp. */
+        $tmpin  = Horde::getTempFile('WebcppIn');
+        $tmpout = Horde::getTempFile('WebcppOut');
+
+        /* Write the contents of our buffer to the temporary input file. */
+        file_put_contents($tmpin, $this->_mimepart->getContents());
+
+        /* Get the extension for the mime type. */
+        include_once dirname(__FILE__) . '/../Magic.php';
+        $ext = Horde_Mime_Magic::MIMEToExt($this->_mimepart->getType());
+
+        /* Execute Web C Plus Plus. Specifying the in and out files didn't
+         * work for me but pipes did. */
+        exec($this->_conf['location'] . " --pipe --pipe -x=$ext -l -a -t < $tmpin > $tmpout");
+        $results = file_get_contents($tmpout);
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $results,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/wordperfect.php b/framework/Mime/lib/Horde/Mime/Viewer/wordperfect.php
new file mode 100644 (file)
index 0000000..3f71b7d
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_wordperfect class renders out WordPerfect documents
+ * in HTML format by using the libwpd package.
+ *
+ * libpwd website: http://libwpd.sourceforge.net/
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Matt Selsky <selsky@columbia.edu>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_wordperfect extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => false,
+        'full' => true,
+        'info' => false,
+        'inline' => false
+    );
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        /* Check to make sure the viewer program exists. */
+        if (!isset($this->_conf['location']) ||
+            !file_exists($this->_conf['location'])) {
+            return array();
+        }
+
+        $tmp_wpd = Horde::getTempFile('wpd');
+        $tmp_output = Horde::getTempFile('wpd');
+
+        file_put_contents($tmp_wpd, $this->_mimepart->getContents());
+
+        exec($this->_conf['location'] . " $tmp_wpd > $tmp_output");
+
+        if (file_exists($tmp_output)) {
+            $data = file_get_contents($tmp_output);
+        } else {
+            $data = _("Unable to translate this WordPerfect document");
+        }
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => $data,
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/Viewer/zip.php b/framework/Mime/lib/Horde/Mime/Viewer/zip.php
new file mode 100644 (file)
index 0000000..af49dbb
--- /dev/null
@@ -0,0 +1,148 @@
+<?php
+/**
+ * The Horde_Mime_Viewer_zip class renders out the contents of ZIP files in
+ * HTML format.
+ *
+ * Copyright 2000-2008 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author  Chuck Hagenbuch <chuck@horde.org>
+ * @author  Michael Cochrane <mike@graftonhall.co.nz>
+ * @package Horde_Mime_Viewer
+ */
+class Horde_Mime_Viewer_zip extends Horde_Mime_Viewer_Driver
+{
+    /**
+     * Can this driver render various views?
+     *
+     * @var boolean
+     */
+    protected $_capability = array(
+        'embedded' => false,
+        'forceinline' => true,
+        'full' => true,
+        'info' => false,
+        'inline' => true
+    );
+
+    /**
+     * A callback function to use in _toHTML().
+     *
+     * @var callback
+     */
+    protected $_callback = null;
+
+    /**
+     * Return the full rendered version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _render()
+    {
+        $ret = $this->_toHTML();
+        if (!empty($ret)) {
+            reset($ret);
+            $ret[key($ret)]['data'] = '<html><body>' . $ret[key($ret)]['data'] . '</body></html>';
+        }
+        return $ret;
+    }
+
+    /**
+     * Return the rendered inline version of the Horde_Mime_Part object.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _renderInline()
+    {
+        return $this->_toHTML();
+    }
+
+    /**
+     * Converts the ZIP file to an HTML display.
+     *
+     * @return array  See Horde_Mime_Viewer_Driver::render().
+     */
+    protected function _toHTML()
+    {
+        $contents = $this->_mimepart->getContents();
+
+        require_once 'Horde/Compress.php';
+        $zip = &Horde_Compress::singleton('zip');
+
+        /* Make sure this is a valid zip file. */
+        if ($zip->checkZipData($contents) === false) {
+            return array();
+        }
+
+        $zipInfo = $zip->decompress($contents, array('action' => HORDE_COMPRESS_ZIP_LIST));
+        if (is_a($zipInfo, 'PEAR_Error')) {
+            return array();
+        }
+        $fileCount = count($zipInfo);
+
+        /* Determine maximum file name length. */
+        $max_array = array();
+        foreach ($zipInfo as $val) {
+            $max_array[] = strlen($val['name']);
+        }
+        $maxlen = empty($max_array) ? 0 : max($max_array);
+
+        require_once 'Horde/Text.php';
+
+        $name = $this->_mimepart->getName(true);
+        if (empty($name)) {
+            $name = _("unnamed");
+        }
+
+        $text = '<strong>' . htmlspecialchars(sprintf(_("Contents of \"%s\""), $name)) . ':</strong>' . "\n" .
+            '<table><tr><td align="left"><tt><span class="fixed">' .
+            Text::htmlAllSpaces(
+                _("Archive Name") . ': ' . $name . "\n" .
+                _("Archive File Size") . ': ' . strlen($contents) .
+                ' bytes' . "\n" .
+                sprintf(ngettext("File Count: %d file", "File Count: %d files", $fileCount), $fileCount) .
+                "\n\n" .
+                String::pad(_("File Name"), $maxlen, ' ', STR_PAD_RIGHT) .
+                String::pad(_("Attributes"), 10, ' ', STR_PAD_LEFT) .
+                String::pad(_("Size"), 10, ' ', STR_PAD_LEFT) .
+                String::pad(_("Modified Date"), 19, ' ', STR_PAD_LEFT) .
+                String::pad(_("Method"), 10, ' ', STR_PAD_LEFT) .
+                String::pad(_("CRC"), 10, ' ', STR_PAD_LEFT) .
+                String::pad(_("Ratio"), 10, ' ', STR_PAD_LEFT) .
+                "\n"
+            ) . str_repeat('-', 69 + $maxlen) . "\n";
+
+        foreach ($zipInfo as $key => $val) {
+            $ratio = (empty($val['size']))
+                ? 0
+                : 100 * ($val['csize'] / $val['size']);
+
+            $val['name']   = String::pad($val['name'], $maxlen, ' ', STR_PAD_RIGHT);
+            $val['attr']   = String::pad($val['attr'], 10,' ', STR_PAD_LEFT);
+            $val['size']   = String::pad($val['size'], 10, ' ', STR_PAD_LEFT);
+            $val['date']   = String::pad(strftime("%d-%b-%Y %H:%M", $val['date']), 19, ' ', STR_PAD_LEFT);
+            $val['method'] = String::pad($val['method'], 10, ' ', STR_PAD_LEFT);
+            $val['crc']    = String::pad($val['crc'], 10, ' ', STR_PAD_LEFT);
+            $val['ratio']  = String::pad(sprintf("%1.1f%%", $ratio), 10, ' ', STR_PAD_LEFT);
+
+            $val = array_map(array('Text', 'htmlAllSpaces'), $val);
+            if (!is_null($this->_callback)) {
+                $val = call_user_func($this->_callback, $key, $val);
+            }
+
+            $text .= $val['name'] . $val['attr'] . $val['size'] .
+                $val['date'] . $val['method'] . $val['crc'] . $val['ratio'] .
+                "\n";
+        }
+
+        return array(
+            $this->_mimepart->getMimeId() => array(
+                'data' => nl2br($text . str_repeat('-', 69 + $maxlen) . "\n" . '</span></tt></td></tr></table>'),
+                'status' => array(),
+                'type' => 'text/html; charset=' . NLS::getCharset()
+            )
+        );
+    }
+}
diff --git a/framework/Mime/lib/Horde/Mime/mime.magic.php b/framework/Mime/lib/Horde/Mime/mime.magic.php
new file mode 100644 (file)
index 0000000..0ee89d2
Binary files /dev/null and b/framework/Mime/lib/Horde/Mime/mime.magic.php differ
diff --git a/framework/Mime/lib/Horde/Mime/mime.mapping.php b/framework/Mime/lib/Horde/Mime/mime.mapping.php
new file mode 100644 (file)
index 0000000..d0ae80e
--- /dev/null
@@ -0,0 +1,892 @@
+<?php
+/**
+ * This file contains a mapping of common file extensions to
+ * MIME types. It has been automatically generated from the
+ * framework/devtools/mime_mapping directory.
+ *
+ * ALL changes should be made to framework/devtools/mime_mapping/mime.types.horde
+ * or else they will be lost when this file is regenerated.
+ *
+ * Any unknown file extensions will automatically be mapped to
+ * 'x-extension/<ext>' where <ext> is the unknown file extension.
+ *
+ * @package Horde_Mime
+ *
+ * Generated: 11/05/08 23:30:23 by chuck on technest.org
+ */
+$mime_extension_map = array(
+    '__MAXPERIOD__' => '1',
+    'ez'            => 'application/andrew-inset',
+    'atom'          => 'application/atom+xml',
+    'atomcat'       => 'application/atomcat+xml',
+    'atomsvc'       => 'application/atomsvc+xml',
+    'ccxml'         => 'application/ccxml+xml',
+    'davmount'      => 'application/davmount+xml',
+    'ecma'          => 'application/ecmascript',
+    'pfr'           => 'application/font-tdpfr',
+    'stk'           => 'application/hyperstudio',
+    'js'            => 'application/x-javascript',
+    'json'          => 'application/json',
+    'hqx'           => 'application/mac-binhex40',
+    'cpt'           => 'application/mac-compactpro',
+    'mrc'           => 'application/marc',
+    'ma'            => 'application/mathematica',
+    'nb'            => 'application/mathematica',
+    'mb'            => 'application/mathematica',
+    'mathml'        => 'application/mathml+xml',
+    'mbox'          => 'application/mbox',
+    'mscml'         => 'application/mediaservercontrol+xml',
+    'mp4s'          => 'application/mp4',
+    'doc'           => 'application/msword',
+    'dot'           => 'application/msword',
+    'mxf'           => 'application/mxf',
+    'bin'           => 'application/octet-stream',
+    'dms'           => 'application/octet-stream',
+    'lha'           => 'application/x-lha',
+    'lzh'           => 'application/x-lha',
+    'class'         => 'application/x-java',
+    'so'            => 'application/x-sharedlib',
+    'iso'           => 'application/x-cd-image',
+    'dmg'           => 'application/octet-stream',
+    'dist'          => 'application/octet-stream',
+    'distz'         => 'application/octet-stream',
+    'pkg'           => 'application/octet-stream',
+    'bpk'           => 'application/octet-stream',
+    'dump'          => 'application/octet-stream',
+    'elc'           => 'application/octet-stream',
+    'oda'           => 'application/oda',
+    'ogg'           => 'application/ogg',
+    'pdf'           => 'application/pdf',
+    'pgp'           => 'application/pgp',
+    'asc'           => 'text/plain',
+    'sig'           => 'application/pgp-signature',
+    'prf'           => 'application/pics-rules',
+    'p10'           => 'application/pkcs10',
+    'p7m'           => 'application/pkcs7-mime',
+    'p7c'           => 'application/pkcs7-mime',
+    'p7s'           => 'application/pkcs7-signature',
+    'cer'           => 'application/x-x509-ca-cert',
+    'crl'           => 'application/pkix-crl',
+    'pkipath'       => 'application/pkix-pkipath',
+    'pki'           => 'application/pkixcmp',
+    'pls'           => 'audio/x-scpls',
+    'ai'            => 'application/illustrator',
+    'eps'           => 'image/x-eps',
+    'ps'            => 'application/postscript',
+    'cww'           => 'application/prs.cww',
+    'rdf'           => 'text/rdf',
+    'rif'           => 'application/reginfo+xml',
+    'rnc'           => 'application/relax-ng-compact-syntax',
+    'rl'            => 'application/resource-lists+xml',
+    'rs'            => 'application/rls-services+xml',
+    'rsd'           => 'application/rsd+xml',
+    'rss'           => 'text/rss',
+    'rtf'           => 'application/rtf',
+    'sbml'          => 'application/sbml+xml',
+    'scq'           => 'application/scvp-cv-request',
+    'scs'           => 'application/scvp-cv-response',
+    'spq'           => 'application/scvp-vp-request',
+    'spp'           => 'application/scvp-vp-response',
+    'sdp'           => 'application/vnd.stardivision.impress',
+    'setpay'        => 'application/set-payment-initiation',
+    'setreg'        => 'application/set-registration-initiation',
+    'shf'           => 'application/shf+xml',
+    'smi'           => 'application/smil',
+    'smil'          => 'application/smil',
+    'rq'            => 'application/sparql-query',
+    'srx'           => 'application/sparql-results+xml',
+    'gram'          => 'application/srgs',
+    'grxml'         => 'application/srgs+xml',
+    'ssml'          => 'application/ssml+xml',
+    'plb'           => 'application/vnd.3gpp.pic-bw-large',
+    'psb'           => 'application/vnd.3gpp.pic-bw-small',
+    'pvb'           => 'application/vnd.3gpp.pic-bw-var',
+    'tcap'          => 'application/vnd.3gpp2.tcap',
+    'pwn'           => 'application/vnd.3m.post-it-notes',
+    'aso'           => 'application/vnd.accpac.simply.aso',
+    'imp'           => 'application/vnd.accpac.simply.imp',
+    'acu'           => 'application/vnd.acucobol',
+    'atc'           => 'application/vnd.acucorp',
+    'acutc'         => 'application/vnd.acucorp',
+    'xdp'           => 'application/vnd.adobe.xdp+xml',
+    'xfdf'          => 'application/vnd.adobe.xfdf',
+    'ami'           => 'application/vnd.amiga.ami',
+    'cii'           => 'application/vnd.anser-web-certificate-issue-initiation',
+    'fti'           => 'application/vnd.anser-web-funds-transfer-initiation',
+    'atx'           => 'application/vnd.antix.game-component',
+    'mpkg'          => 'application/vnd.apple.installer+xml',
+    'aep'           => 'application/vnd.audiograph',
+    'mpm'           => 'application/vnd.blueice.multipass',
+    'bmi'           => 'application/vnd.bmi',
+    'rep'           => 'application/vnd.businessobjects',
+    'cdxml'         => 'application/vnd.chemdraw+xml',
+    'mmd'           => 'application/vnd.chipnuts.karaoke-mmd',
+    'cdy'           => 'application/vnd.cinderella',
+    'cla'           => 'application/vnd.claymore',
+    'c4g'           => 'application/vnd.clonk.c4group',
+    'c4d'           => 'application/vnd.clonk.c4group',
+    'c4f'           => 'application/vnd.clonk.c4group',
+    'c4p'           => 'application/vnd.clonk.c4group',
+    'c4u'           => 'application/vnd.clonk.c4group',
+    'csp'           => 'application/vnd.commonspace',
+    'cst'           => 'application/vnd.commonspace',
+    'cdbcmsg'       => 'application/vnd.contact.cmsg',
+    'cmc'           => 'application/vnd.cosmocaller',
+    'clkx'          => 'application/vnd.crick.clicker',
+    'clkk'          => 'application/vnd.crick.clicker.keyboard',
+    'clkp'          => 'application/vnd.crick.clicker.palette',
+    'clkt'          => 'application/vnd.crick.clicker.template',
+    'clkw'          => 'application/vnd.crick.clicker.wordbank',
+    'wbs'           => 'application/vnd.criticaltools.wbs+xml',
+    'pml'           => 'application/vnd.ctc-posml',
+    'ppd'           => 'application/vnd.cups-ppd',
+    'curl'          => 'application/vnd.curl',
+    'rdz'           => 'application/vnd.data-vision.rdz',
+    'fe_launch'     => 'application/vnd.denovo.fcselayout-link',
+    'dna'           => 'application/vnd.dna',
+    'mlp'           => 'application/vnd.dolby.mlp',
+    'dpg'           => 'application/vnd.dpgraph',
+    'dfac'          => 'application/vnd.dreamfactory',
+    'mag'           => 'application/vnd.ecowin.chart',
+    'nml'           => 'application/vnd.enliven',
+    'esf'           => 'application/vnd.epson.esf',
+    'msf'           => 'application/vnd.epson.msf',
+    'qam'           => 'application/vnd.epson.quickanime',
+    'slt'           => 'application/vnd.epson.salt',
+    'ssf'           => 'application/vnd.epson.ssf',
+    'es3'           => 'application/vnd.eszigno3+xml',
+    'et3'           => 'application/vnd.eszigno3+xml',
+    'ez2'           => 'application/vnd.ezpix-album',
+    'ez3'           => 'application/vnd.ezpix-package',
+    'fdf'           => 'application/vnd.fdf',
+    'gph'           => 'application/vnd.flographit',
+    'ftc'           => 'application/vnd.fluxtime.clip',
+    'fm'            => 'application/vnd.framemaker',
+    'frame'         => 'application/vnd.framemaker',
+    'maker'         => 'application/vnd.framemaker',
+    'fnc'           => 'application/vnd.frogans.fnc',
+    'ltf'           => 'application/vnd.frogans.ltf',
+    'fsc'           => 'application/vnd.fsc.weblaunch',
+    'oas'           => 'application/vnd.fujitsu.oasys',
+    'oa2'           => 'application/vnd.fujitsu.oasys2',
+    'oa3'           => 'application/vnd.fujitsu.oasys3',
+    'fg5'           => 'application/vnd.fujitsu.oasysgp',
+    'bh2'           => 'application/vnd.fujitsu.oasysprs',
+    'ddd'           => 'application/vnd.fujixerox.ddd',
+    'xdw'           => 'application/vnd.fujixerox.docuworks',
+    'xbd'           => 'application/vnd.fujixerox.docuworks.binder',
+    'fzs'           => 'application/vnd.fuzzysheet',
+    'txd'           => 'application/vnd.genomatix.tuxedo',
+    'kml'           => 'application/vnd.google-earth.kml+xml',
+    'kmz'           => 'application/vnd.google-earth.kmz',
+    'gqf'           => 'application/vnd.grafeq',
+    'gqs'           => 'application/vnd.grafeq',
+    'gac'           => 'application/vnd.groove-account',
+    'ghf'           => 'application/vnd.groove-help',
+    'gim'           => 'application/vnd.groove-identity-message',
+    'grv'           => 'application/vnd.groove-injector',
+    'gtm'           => 'application/vnd.groove-tool-message',
+    'tpl'           => 'application/vnd.groove-tool-template',
+    'vcg'           => 'application/vnd.groove-vcard',
+    'zmm'           => 'application/vnd.handheld-entertainment+xml',
+    'hbci'          => 'application/vnd.hbci',
+    'les'           => 'application/vnd.hhe.lesson-player',
+    'hpgl'          => 'application/vnd.hp-hpgl',
+    'hpid'          => 'application/vnd.hp-hpid',
+    'hps'           => 'application/vnd.hp-hps',
+    'jlt'           => 'application/vnd.hp-jlyt',
+    'pcl'           => 'application/vnd.hp-pcl',
+    'pclxl'         => 'application/vnd.hp-pclxl',
+    'x3d'           => 'application/vnd.hzn-3d-crossword',
+    'mpy'           => 'application/vnd.ibm.minipay',
+    'afp'           => 'application/vnd.ibm.modcap',
+    'listafp'       => 'application/vnd.ibm.modcap',
+    'list3820'      => 'application/vnd.ibm.modcap',
+    'irm'           => 'application/vnd.ibm.rights-management',
+    'sc'            => 'application/vnd.ibm.secure-container',
+    'igl'           => 'application/vnd.igloader',
+    'ivp'           => 'application/vnd.immervision-ivp',
+    'ivu'           => 'application/vnd.immervision-ivu',
+    'xpw'           => 'application/vnd.intercon.formnet',
+    'xpx'           => 'application/vnd.intercon.formnet',
+    'qbo'           => 'application/vnd.intu.qbo',
+    'qfx'           => 'application/vnd.intu.qfx',
+    'rcprofile'     => 'application/vnd.ipunplugged.rcprofile',
+    'irp'           => 'application/vnd.irepository.package+xml',
+    'xpr'           => 'application/vnd.is-xpr',
+    'jam'           => 'application/vnd.jam',
+    'rms'           => 'application/vnd.jcp.javame.midlet-rms',
+    'jisp'          => 'application/vnd.jisp',
+    'joda'          => 'application/vnd.joost.joda-archive',
+    'ktz'           => 'application/vnd.kahootz',
+    'ktr'           => 'application/vnd.kahootz',
+    'karbon'        => 'application/x-karbon',
+    'chrt'          => 'application/x-kchart',
+    'kfo'           => 'application/x-kformula',
+    'flw'           => 'application/x-kivio',
+    'kon'           => 'application/x-kontour',
+    'kpr'           => 'application/x-kpresenter',
+    'kpt'           => 'application/x-kpresenter',
+    'ksp'           => 'application/x-kspread',
+    'kwd'           => 'application/x-kword',
+    'kwt'           => 'application/x-kword',
+    'htke'          => 'application/vnd.kenameaapp',
+    'kia'           => 'application/vnd.kidspiration',
+    'kne'           => 'application/vnd.kinar',
+    'knp'           => 'application/vnd.kinar',
+    'skp'           => 'application/vnd.koan',
+    'skd'           => 'application/vnd.koan',
+    'skt'           => 'application/vnd.koan',
+    'skm'           => 'application/vnd.koan',
+    'lbd'           => 'application/vnd.llamagraphics.life-balance.desktop',
+    'lbe'           => 'application/vnd.llamagraphics.life-balance.exchange+xml',
+    '123'           => 'application/vnd.lotus-1-2-3',
+    'apr'           => 'application/vnd.lotus-approach',
+    'pre'           => 'application/vnd.lotus-freelance',
+    'nsf'           => 'application/vnd.lotus-notes',
+    'org'           => 'application/vnd.lotus-organizer',
+    'scm'           => 'text/x-scheme',
+    'lwp'           => 'application/vnd.lotus-wordpro',
+    'portpkg'       => 'application/vnd.macports.portpkg',
+    'mcd'           => 'application/vnd.mcd',
+    'mc1'           => 'application/vnd.medcalcdata',
+    'cdkey'         => 'application/vnd.mediastation.cdkey',
+    'mwf'           => 'application/vnd.mfer',
+    'mfm'           => 'application/vnd.mfmp',
+    'flo'           => 'application/vnd.micrografx.flo',
+    'igx'           => 'application/vnd.micrografx.igx',
+    'mif'           => 'application/x-mif',
+    'daf'           => 'application/vnd.mobius.daf',
+    'dis'           => 'application/vnd.mobius.dis',
+    'mbk'           => 'application/vnd.mobius.mbk',
+    'mqy'           => 'application/vnd.mobius.mqy',
+    'msl'           => 'application/vnd.mobius.msl',
+    'plc'           => 'application/vnd.mobius.plc',
+    'txf'           => 'application/vnd.mobius.txf',
+    'mpn'           => 'application/vnd.mophun.application',
+    'mpc'           => 'application/vnd.mophun.certificate',
+    'xul'           => 'application/vnd.mozilla.xul+xml',
+    'cil'           => 'application/vnd.ms-artgalry',
+    'asf'           => 'video/x-ms-asf',
+    'cab'           => 'application/vnd.ms-cab-compressed',
+    'xls'           => 'application/vnd.ms-excel',
+    'xlm'           => 'application/vnd.ms-excel',
+    'xla'           => 'application/vnd.ms-excel',
+    'xlc'           => 'application/vnd.ms-excel',
+    'xlt'           => 'application/vnd.ms-excel',
+    'xlw'           => 'application/vnd.ms-excel',
+    'eot'           => 'application/vnd.ms-fontobject',
+    'chm'           => 'application/x-chm',
+    'ims'           => 'application/vnd.ms-ims',
+    'lrm'           => 'application/vnd.ms-lrm',
+    'ppt'           => 'application/vnd.ms-powerpoint',
+    'pps'           => 'application/vnd.ms-powerpoint',
+    'pot'           => 'text/x-gettext-translation-template',
+    'mpp'           => 'application/vnd.ms-project',
+    'mpt'           => 'application/vnd.ms-project',
+    'wps'           => 'application/vnd.ms-works',
+    'wks'           => 'application/vnd.lotus-1-2-3',
+    'wcm'           => 'application/vnd.ms-works',
+    'wdb'           => 'application/vnd.ms-works',
+    'wpl'           => 'application/vnd.ms-wpl',
+    'xps'           => 'application/vnd.ms-xpsdocument',
+    'mseq'          => 'application/vnd.mseq',
+    'mus'           => 'application/vnd.musician',
+    'msty'          => 'application/vnd.muvee.style',
+    'nlu'           => 'application/vnd.neurolanguage.nlu',
+    'nnd'           => 'application/vnd.noblenet-directory',
+    'nns'           => 'application/vnd.noblenet-sealer',
+    'nnw'           => 'application/vnd.noblenet-web',
+    'ngdat'         => 'application/vnd.nokia.n-gage.data',
+    'n-gage'        => 'application/vnd.nokia.n-gage.symbian.install',
+    'rpst'          => 'application/vnd.nokia.radio-preset',
+    'rpss'          => 'application/vnd.nokia.radio-presets',
+    'edm'           => 'application/vnd.novadigm.edm',
+    'edx'           => 'application/vnd.novadigm.edx',
+    'ext'           => 'application/vnd.novadigm.ext',
+    'odc'           => 'application/vnd.oasis.opendocument.chart',
+    'otc'           => 'application/vnd.oasis.opendocument.chart-template',
+    'odf'           => 'application/vnd.oasis.opendocument.formula',
+    'otf'           => 'application/vnd.oasis.opendocument.formula-template',
+    'odg'           => 'application/vnd.oasis.opendocument.graphics',
+    'otg'           => 'application/vnd.oasis.opendocument.graphics-template',
+    'odi'           => 'application/vnd.oasis.opendocument.image',
+    'oti'           => 'application/vnd.oasis.opendocument.image-template',
+    'odp'           => 'application/vnd.oasis.opendocument.presentation',
+    'otp'           => 'application/vnd.oasis.opendocument.presentation-template',
+    'ods'           => 'application/vnd.oasis.opendocument.spreadsheet',
+    'ots'           => 'application/vnd.oasis.opendocument.spreadsheet-template',
+    'odt'           => 'application/vnd.oasis.opendocument.text',
+    'otm'           => 'application/vnd.oasis.opendocument.text-master',
+    'ott'           => 'application/vnd.oasis.opendocument.text-template',
+    'oth'           => 'application/vnd.oasis.opendocument.text-web',
+    'xo'            => 'application/vnd.olpc-sugar',
+    'dd2'           => 'application/vnd.oma.dd2+xml',
+    'oxt'           => 'application/vnd.openofficeorg.extension',
+    'dp'            => 'application/vnd.osgi.dp',
+    'prc'           => 'application/vnd.palm',
+    'pdb'           => 'application/vnd.palm',
+    'pqa'           => 'application/vnd.palm',
+    'oprc'          => 'application/vnd.palm',
+    'str'           => 'application/vnd.pg.format',
+    'ei6'           => 'application/vnd.pg.osasli',
+    'efif'          => 'application/vnd.picsel',
+    'plf'           => 'application/vnd.pocketlearn',
+    'pbd'           => 'application/vnd.powerbuilder6',
+    'box'           => 'application/vnd.previewsystems.box',
+    'mgz'           => 'application/vnd.proteus.magazine',
+    'qps'           => 'application/vnd.publishare-delta-tree',
+    'ptid'          => 'application/vnd.pvi.ptid1',
+    'qxd'           => 'application/vnd.quark.quarkxpress',
+    'qxt'           => 'application/vnd.quark.quarkxpress',
+    'qwd'           => 'application/vnd.quark.quarkxpress',
+    'qwt'           => 'application/vnd.quark.quarkxpress',
+    'qxl'           => 'application/vnd.quark.quarkxpress',
+    'qxb'           => 'application/vnd.quark.quarkxpress',
+    'mxl'           => 'application/vnd.recordare.musicxml',
+    'rm'            => 'audio/x-pn-realaudio',
+    'see'           => 'application/vnd.seemail',
+    'sema'          => 'application/vnd.sema',
+    'semd'          => 'application/vnd.semd',
+    'semf'          => 'application/vnd.semf',
+    'ifm'           => 'application/vnd.shana.informed.formdata',
+    'itp'           => 'application/vnd.shana.informed.formtemplate',
+    'iif'           => 'application/vnd.shana.informed.interchange',
+    'ipk'           => 'application/vnd.shana.informed.package',
+    'twd'           => 'application/vnd.simtech-mindmapper',
+    'twds'          => 'application/vnd.simtech-mindmapper',
+    'mmf'           => 'application/vnd.smaf',
+    'sdkm'          => 'application/vnd.solent.sdkm+xml',
+    'sdkd'          => 'application/vnd.solent.sdkm+xml',
+    'dxp'           => 'application/vnd.spotfire.dxp',
+    'sfs'           => 'application/vnd.spotfire.sfs',
+    'sus'           => 'application/vnd.sus-calendar',
+    'susp'          => 'application/vnd.sus-calendar',
+    'svd'           => 'application/vnd.svd',
+    'xsm'           => 'application/vnd.syncml+xml',
+    'bdm'           => 'application/vnd.syncml.dm+wbxml',
+    'xdm'           => 'application/vnd.syncml.dm+xml',
+    'tao'           => 'application/vnd.tao.intent-module-archive',
+    'tmo'           => 'application/vnd.tmobile-livetv',
+    'tpt'           => 'application/vnd.trid.tpt',
+    'mxs'           => 'application/vnd.triscape.mxs',
+    'tra'           => 'application/vnd.trueapp',
+    'ufd'           => 'application/vnd.ufdl',
+    'ufdl'          => 'application/vnd.ufdl',
+    'utz'           => 'application/vnd.uiq.theme',
+    'umj'           => 'application/vnd.umajin',
+    'unityweb'      => 'application/vnd.unity',
+    'uoml'          => 'application/vnd.uoml+xml',
+    'vcx'           => 'application/vnd.vcx',
+    'vsd'           => 'application/vnd.visio',
+    'vst'           => 'application/vnd.visio',
+    'vss'           => 'application/vnd.visio',
+    'vsw'           => 'application/vnd.visio',
+    'vis'           => 'application/vnd.visionary',
+    'vsf'           => 'application/vnd.vsf',
+    'wbxml'         => 'application/vnd.wap.wbxml',
+    'wmlc'          => 'application/vnd.wap.wmlc',
+    'wmlsc'         => 'application/vnd.wap.wmlscriptc',
+    'wtb'           => 'application/vnd.webturbo',
+    'wpd'           => 'application/vnd.wordperfect',
+    'wqd'           => 'application/vnd.wqd',
+    'stf'           => 'application/vnd.wt.stf',
+    'xar'           => 'application/vnd.xara',
+    'xfdl'          => 'application/vnd.xfdl',
+    'hvd'           => 'application/vnd.yamaha.hv-dic',
+    'hvs'           => 'application/vnd.yamaha.hv-script',
+    'hvp'           => 'application/vnd.yamaha.hv-voice',
+    'saf'           => 'application/vnd.yamaha.smaf-audio',
+    'spf'           => 'application/vnd.yamaha.smaf-phrase',
+    'cmp'           => 'application/vnd.yellowriver-custom-menu',
+    'zaz'           => 'application/vnd.zzazz.deck+xml',
+    'vxml'          => 'application/voicexml+xml',
+    'hlp'           => 'application/winhlp',
+    'wsdl'          => 'application/wsdl+xml',
+    'wspolicy'      => 'application/wspolicy+xml',
+    'ace'           => 'application/x-ace-compressed',
+    'bcpio'         => 'application/x-bcpio',
+    'torrent'       => 'application/x-bittorrent',
+    'bz'            => 'application/x-bzip',
+    'bz2'           => 'application/x-bzip',
+    'boz'           => 'application/x-bzip2',
+    'vcd'           => 'application/x-cdlink',
+    'chat'          => 'application/x-chat',
+    'pgn'           => 'application/x-chess-pgn',
+    'cpio'          => 'application/x-cpio',
+    'csh'           => 'application/x-csh',
+    'dcr'           => 'application/x-director',
+    'dir'           => 'application/x-director',
+    'dxr'           => 'application/x-director',
+    'fgd'           => 'application/x-director',
+    'dvi'           => 'application/x-dvi',
+    'spl'           => 'application/x-futuresplash',
+    'gtar'          => 'application/x-gtar',
+    'hdf'           => 'application/x-hdf',
+    'latex'         => 'application/x-latex',
+    'wmd'           => 'application/x-ms-wmd',
+    'wmz'           => 'application/x-ms-wmz',
+    'mdb'           => 'application/x-msaccess',
+    'obd'           => 'application/x-msbinder',
+    'crd'           => 'application/x-mscardfile',
+    'clp'           => 'application/x-msclip',
+    'exe'           => 'application/x-ms-dos-executable',
+    'dll'           => 'application/x-msdownload',
+    'com'           => 'application/x-msdownload',
+    'bat'           => 'application/x-msdownload',
+    'msi'           => 'application/x-msdownload',
+    'mvb'           => 'application/x-msmediaview',
+    'm13'           => 'application/x-msmediaview',
+    'm14'           => 'application/x-msmediaview',
+    'wmf'           => 'image/x-wmf',
+    'mny'           => 'application/x-msmoney',
+    'pub'           => 'application/x-mspublisher',
+    'scd'           => 'application/x-msschedule',
+    'trm'           => 'application/x-msterminal',
+    'wri'           => 'application/x-mswrite',
+    'nc'            => 'application/x-netcdf',
+    'cdf'           => 'application/x-netcdf',
+    'p12'           => 'application/x-pkcs12',
+    'pfx'           => 'application/x-pkcs12',
+    'p7b'           => 'application/x-pkcs7-certificates',
+    'spc'           => 'application/x-pkcs7-certificates',
+    'p7r'           => 'application/x-pkcs7-certreqresp',
+    'rar'           => 'application/x-rar',
+    'sh'            => 'application/x-shellscript',
+    'shar'          => 'application/x-shar',
+    'swf'           => 'application/x-shockwave-flash',
+    'sit'           => 'application/stuffit',
+    'sitx'          => 'application/x-stuffitx',
+    'sv4cpio'       => 'application/x-sv4cpio',
+    'sv4crc'        => 'application/x-sv4crc',
+    'tar'           => 'application/x-tar',
+    'tcl'           => 'text/x-tcl',
+    'tex'           => 'text/x-tex',
+    'texinfo'       => 'text/x-texinfo',
+    'texi'          => 'text/x-texinfo',
+    'ustar'         => 'application/x-ustar',
+    'src'           => 'application/x-wais-source',
+    'der'           => 'application/x-x509-ca-cert',
+    'crt'           => 'application/x-x509-ca-cert',
+    'xenc'          => 'application/xenc+xml',
+    'xhtml'         => 'application/xhtml+xml',
+    'xht'           => 'application/xhtml+xml',
+    'xml'           => 'text/xml',
+    'xsl'           => 'text/x-xslt',
+    'dtd'           => 'text/x-dtd',
+    'xop'           => 'application/xop+xml',
+    'xslt'          => 'text/x-xslt',
+    'xspf'          => 'application/xspf+xml',
+    'mxml'          => 'application/xv+xml',
+    'xhvml'         => 'application/xv+xml',
+    'xvml'          => 'application/xv+xml',
+    'xvm'           => 'application/xv+xml',
+    'zip'           => 'application/zip',
+    'au'            => 'audio/basic',
+    'snd'           => 'audio/basic',
+    'mid'           => 'audio/midi',
+    'midi'          => 'audio/midi',
+    'kar'           => 'audio/midi',
+    'rmi'           => 'audio/midi',
+    'mp4a'          => 'audio/mp4',
+    'mpga'          => 'audio/mpeg',
+    'mp2'           => 'video/mpeg',
+    'mp2a'          => 'audio/mpeg',
+    'mp3'           => 'audio/mpeg',
+    'm2a'           => 'audio/mpeg',
+    'm3a'           => 'audio/mpeg',
+    'eol'           => 'audio/vnd.digital-winds',
+    'lvp'           => 'audio/vnd.lucent.voice',
+    'ecelp4800'     => 'audio/vnd.nuera.ecelp4800',
+    'ecelp7470'     => 'audio/vnd.nuera.ecelp7470',
+    'ecelp9600'     => 'audio/vnd.nuera.ecelp9600',
+    'wav'           => 'audio/x-wav',
+    'aif'           => 'audio/x-aiff',
+    'aiff'          => 'audio/x-aiff',
+    'aifc'          => 'audio/x-aiff',
+    'm3u'           => 'audio/x-mpegurl',
+    'wax'           => 'audio/x-ms-wax',
+    'wma'           => 'audio/x-ms-wma',
+    'ram'           => 'audio/x-pn-realaudio',
+    'ra'            => 'audio/x-pn-realaudio',
+    'rmp'           => 'audio/x-pn-realaudio-plugin',
+    'cdx'           => 'chemical/x-cdx',
+    'cif'           => 'chemical/x-cif',
+    'cmdf'          => 'chemical/x-cmdf',
+    'cml'           => 'chemical/x-cml',
+    'csml'          => 'chemical/x-csml',
+    'xyz'           => 'chemical/x-xyz',
+    'bmp'           => 'image/bmp',
+    'cgm'           => 'image/cgm',
+    'g3'            => 'image/fax-g3',
+    'gif'           => 'image/gif',
+    'ief'           => 'image/ief',
+    'jpg'           => 'image/jpeg',
+    'jpeg'          => 'image/jpeg',
+    'jpe'           => 'image/jpeg',
+    'png'           => 'image/png',
+    'btif'          => 'image/prs.btif',
+    'svg'           => 'image/svg+xml',
+    'svgz'          => 'image/svg+xml',
+    'tiff'          => 'image/tiff',
+    'tif'           => 'image/tiff',
+    'psd'           => 'image/x-psd',
+    'djvu'          => 'image/vnd.djvu',
+    'djv'           => 'image/vnd.djvu',
+    'dwg'           => 'image/vnd.dwg',
+    'dxf'           => 'image/vnd.dxf',
+    'fbs'           => 'image/vnd.fastbidsheet',
+    'fpx'           => 'image/vnd.fpx',
+    'fst'           => 'image/vnd.fst',
+    'mmr'           => 'image/vnd.fujixerox.edmics-mmr',
+    'rlc'           => 'image/vnd.fujixerox.edmics-rlc',
+    'mdi'           => 'image/vnd.ms-modi',
+    'npx'           => 'image/vnd.net-fpx',
+    'wbmp'          => 'image/vnd.wap.wbmp',
+    'xif'           => 'image/vnd.xiff',
+    'ras'           => 'image/x-cmu-raster',
+    'cmx'           => 'image/x-cmx',
+    'ico'           => 'image/x-ico',
+    'pcx'           => 'image/x-pcx',
+    'pic'           => 'image/x-pict',
+    'pct'           => 'image/x-pict',
+    'pnm'           => 'image/x-portable-anymap',
+    'pbm'           => 'image/x-portable-bitmap',
+    'pgm'           => 'image/x-portable-graymap',
+    'ppm'           => 'image/x-portable-pixmap',
+    'rgb'           => 'image/x-rgb',
+    'xbm'           => 'image/x-xbitmap',
+    'xpm'           => 'image/x-xpixmap',
+    'xwd'           => 'image/x-xwindowdump',
+    'eml'           => 'message/rfc822',
+    'mime'          => 'message/rfc822',
+    'igs'           => 'model/iges',
+    'iges'          => 'model/iges',
+    'msh'           => 'model/mesh',
+    'mesh'          => 'model/mesh',
+    'silo'          => 'model/mesh',
+    'dwf'           => 'model/vnd.dwf',
+    'gdl'           => 'model/vnd.gdl',
+    'gtw'           => 'model/vnd.gtw',
+    'mts'           => 'model/vnd.mts',
+    'vtu'           => 'model/vnd.vtu',
+    'wrl'           => 'model/vrml',
+    'vrml'          => 'model/vrml',
+    'ics'           => 'text/calendar',
+    'ifb'           => 'text/calendar',
+    'css'           => 'text/css',
+    'csv'           => 'text/x-comma-separated-values',
+    'html'          => 'text/html',
+    'htm'           => 'text/html',
+    'txt'           => 'text/plain',
+    'text'          => 'text/plain',
+    'conf'          => 'text/plain',
+    'def'           => 'text/plain',
+    'list'          => 'text/plain',
+    'log'           => 'text/x-log',
+    'in'            => 'text/plain',
+    'dsc'           => 'text/prs.lines.tag',
+    'rtx'           => 'text/richtext',
+    'sgml'          => 'text/sgml',
+    'sgm'           => 'text/sgml',
+    'tsv'           => 'text/tab-separated-values',
+    't'             => 'application/x-troff',
+    'tr'            => 'application/x-troff',
+    'roff'          => 'application/x-troff',
+    'man'           => 'application/x-troff-man',
+    'me'            => 'text/x-troff-me',
+    'ms'            => 'text/x-troff-ms',
+    'uri'           => 'text/x-uri',
+    'uris'          => 'text/uri-list',
+    'urls'          => 'text/uri-list',
+    'fly'           => 'text/vnd.fly',
+    'flx'           => 'text/vnd.fmi.flexstor',
+    '3dml'          => 'text/vnd.in3d.3dml',
+    'spot'          => 'text/vnd.in3d.spot',
+    'jad'           => 'text/vnd.sun.j2me.app-descriptor',
+    'wml'           => 'text/vnd.wap.wml',
+    'wmls'          => 'text/vnd.wap.wmlscript',
+    's'             => 'text/x-asm',
+    'asm'           => 'text/x-asm',
+    'c'             => 'text/x-csrc',
+    'cc'            => 'text/x-c++src',
+    'cxx'           => 'text/x-c++src',
+    'cpp'           => 'text/x-c++src',
+    'h'             => 'text/x-chdr',
+    'hh'            => 'text/x-c++hdr',
+    'dic'           => 'text/x-c',
+    'f'             => 'text/x-fortran',
+    'for'           => 'text/x-fortran',
+    'f77'           => 'text/x-fortran',
+    'f90'           => 'text/x-fortran',
+    'p'             => 'text/x-pascal',
+    'pas'           => 'text/x-pascal',
+    'java'          => 'text/x-java',
+    'etx'           => 'text/x-setext',
+    'uu'            => 'text/x-uuencode',
+    'vcs'           => 'text/calendar',
+    'vcf'           => 'text/directory',
+    '3gp'           => 'video/3gpp',
+    '3g2'           => 'video/3gpp2',
+    'h261'          => 'video/h261',
+    'h263'          => 'video/h263',
+    'h264'          => 'video/h264',
+    'jpgv'          => 'video/jpeg',
+    'jpm'           => 'video/jpm',
+    'jpgm'          => 'video/jpm',
+    'mj2'           => 'video/mj2',
+    'mjp2'          => 'video/mj2',
+    'mp4'           => 'video/mp4',
+    'mp4v'          => 'video/mp4',
+    'mpg4'          => 'video/mp4',
+    'mpeg'          => 'video/mpeg',
+    'mpg'           => 'video/mpeg',
+    'mpe'           => 'video/mpeg',
+    'm1v'           => 'video/mpeg',
+    'm2v'           => 'video/mpeg',
+    'qt'            => 'video/quicktime',
+    'mov'           => 'video/quicktime',
+    'fvt'           => 'video/vnd.fvt',
+    'mxu'           => 'video/vnd.mpegurl',
+    'm4u'           => 'video/vnd.mpegurl',
+    'viv'           => 'video/vnd.vivo',
+    'fli'           => 'video/x-flic',
+    'asx'           => 'video/x-ms-asf',
+    'wm'            => 'video/x-ms-wm',
+    'wmv'           => 'video/x-ms-wmv',
+    'wmx'           => 'video/x-ms-wmx',
+    'wvx'           => 'video/x-ms-wvx',
+    'avi'           => 'video/x-msvideo',
+    'movie'         => 'video/x-sgi-movie',
+    'ice'           => 'x-conference/x-cooltalk',
+    'odb'           => 'application/vnd.oasis.opendocument.database',
+    'oot'           => 'application/vnd.oasis.opendocument.text',
+    'odm'           => 'application/vnd.oasis.opendocument.text-master',
+    'sxc'           => 'application/vnd.sun.xml.calc',
+    'stc'           => 'application/vnd.sun.xml.calc.template',
+    'sxd'           => 'application/vnd.sun.xml.draw',
+    'std'           => 'application/vnd.sun.xml.draw.template',
+    'sxg'           => 'application/vnd.sun.xml.writer.global',
+    'sxm'           => 'application/vnd.sun.xml.math',
+    'sxi'           => 'application/vnd.sun.xml.impress',
+    'sti'           => 'application/vnd.sun.xml.impress.template',
+    'sxw'           => 'application/vnd.sun.xml.writer',
+    'stw'           => 'application/vnd.sun.xml.writer.template',
+    'Z'             => 'application/x-compress',
+    'gz'            => 'application/x-gzip',
+    'tgz'           => 'application/x-compressed-tar',
+    'php'           => 'application/x-php',
+    'php3'          => 'application/x-php',
+    'pl'            => 'application/x-perl',
+    'pm'            => 'application/x-perl',
+    'gsm'           => 'audio/x-gsm',
+    'vfb'           => 'text/calendar',
+    'diff'          => 'text/x-patch',
+    'patch'         => 'text/x-patch',
+    'shtml'         => 'text/html',
+    'po'            => 'text/x-gettext-translation',
+    'sgl'           => 'application/vnd.stardivision.writer',
+    'wk4'           => 'application/vnd.lotus-1-2-3',
+    'pict2'         => 'image/x-pict',
+    'lhz'           => 'application/x-lhz',
+    'tar.bz2'       => 'application/x-bzip-compressed-tar',
+    'rle'           => 'image/rle',
+    'pcf.Z'         => 'application/x-font-type1',
+    'm15'           => 'audio/x-mod',
+    'flac'          => 'audio/x-flac',
+    'dc'            => 'application/x-dc-rom',
+    'm'             => 'text/x-objcsrc',
+    'o'             => 'application/x-object',
+    'fits'          => 'image/x-fits',
+    'pfa'           => 'application/x-font-type1',
+    'jnlp'          => 'application/x-java-jnlp-file',
+    'pfb'           => 'application/x-font-type1',
+    'smd'           => 'application/vnd.stardivision.mail',
+    'it'            => 'audio/x-it',
+    'bib'           => 'text/x-bibtex',
+    'moc'           => 'text/x-moc',
+    'theme'         => 'application/x-theme',
+    'mod'           => 'audio/x-mod',
+    'smf'           => 'application/vnd.stardivision.math',
+    'uni'           => 'audio/x-mod',
+    'mtm'           => 'audio/x-mod',
+    'ppz'           => 'application/vnd.ms-powerpoint',
+    's3m'           => 'audio/x-s3m',
+    'deb'           => 'application/x-deb',
+    'tk'            => 'text/x-tcl',
+    'cdr'           => 'application/vnd.corel-draw',
+    'lwob'          => 'image/x-lwo',
+    'sml'           => 'application/smil',
+    'etheme'        => 'application/x-e-theme',
+    '3ds'           => 'image/x-3ds',
+    'vob'           => 'video/mpeg',
+    'voc'           => 'audio/x-voc',
+    'bdf'           => 'application/x-font-bdf',
+    'ps.gz'         => 'application/x-gzpostscript',
+    'uil'           => 'text/x-uil',
+    'ts'            => 'application/x-linguist',
+    'asp'           => 'application/x-asp',
+    'nes'           => 'application/x-nes-rom',
+    'sms'           => 'application/x-sms-rom',
+    'BLEND'         => 'application/x-blender',
+    'kil'           => 'application/x-killustrator',
+    'icb'           => 'image/x-icb',
+    'lyx'           => 'application/x-lyx',
+    'jng'           => 'image/x-jng',
+    'vor'           => 'application/vnd.stardivision.writer',
+    'adb'           => 'text/x-adasrc',
+    'flc'           => 'video/x-flic',
+    'wpg'           => 'application/x-wpg',
+    'wb1'           => 'application/x-quattropro',
+    'gra'           => 'application/x-graphite',
+    'wb2'           => 'application/x-quattropro',
+    'ltx'           => 'text/x-tex',
+    'xac'           => 'application/x-gnucash',
+    'wb3'           => 'application/x-quattropro',
+    'epsf'          => 'image/x-eps',
+    'el'            => 'text/x-emacs-lisp',
+    'jp2'           => 'image/jpeg2000',
+    'tar.gz'        => 'application/x-compressed-tar',
+    'epsi'          => 'image/x-eps',
+    'ui'            => 'application/x-designer',
+    'old'           => 'application/x-trash',
+    'tar.Z'         => 'application/x-tarz',
+    'ttf'           => 'application/x-font-ttf',
+    'siag'          => 'application/x-siag',
+    'sid'           => 'audio/prs.sid',
+    'msod'          => 'image/x-msod',
+    'h++'           => 'text/x-chdr',
+    'tar.lzo'       => 'application/x-tzo',
+    'tar.bz'        => 'application/x-bzip-compressed-tar',
+    'ads'           => 'text/x-adasrc',
+    'sda'           => 'application/vnd.stardivision.draw',
+    'lzo'           => 'application/x-lzop',
+    'cur'           => 'image/x-win-bitmap',
+    'sdc'           => 'application/vnd.stardivision.calc',
+    'sik'           => 'application/x-trash',
+    'sdd'           => 'application/vnd.stardivision.impress',
+    'xld'           => 'application/vnd.ms-excel',
+    'gmo'           => 'application/x-gettext-translation',
+    'xll'           => 'application/vnd.ms-excel',
+    'blend'         => 'application/x-blender',
+    'pw'            => 'application/x-pw',
+    'kud'           => 'application/x-kugar',
+    'mkv'           => 'application/x-matroska',
+    'obj'           => 'application/x-tgif',
+    'py'            => 'text/x-python',
+    'sds'           => 'application/vnd.stardivision.chart',
+    'idl'           => 'text/x-idl',
+    'dat'           => 'video/mpeg',
+    'stm'           => 'audio/x-stm',
+    'PAR2'          => 'application/x-par2',
+    'xcf.bz2'       => 'image/x-compressed-xcf',
+    'psid'          => 'audio/prs.sid',
+    'pict'          => 'image/x-pict',
+    'ag'            => 'image/x-applix-graphics',
+    'fo'            => 'text/x-xslfo',
+    'sdw'           => 'application/vnd.stardivision.writer',
+    'abw.CRASHED'   => 'application/x-abiword',
+    'gsf'           => 'application/x-font-type1',
+    'xcf.gz'        => 'image/x-compressed-xcf',
+    'pcd'           => 'image/x-photo-cd',
+    'egon'          => 'application/x-egon',
+    'pcf'           => 'application/x-font-pcf',
+    'al'            => 'application/x-perl',
+    'gnc'           => 'application/x-gnucash',
+    'tzo'           => 'application/x-tzo',
+    'la'            => 'application/x-shared-library-la',
+    'kpm'           => 'application/x-kpovmodeler',
+    'qif'           => 'application/x-qw',
+    'sty'           => 'text/x-tex',
+    'psf'           => 'application/x-font-linux-psf',
+    'as'            => 'application/x-applix-spreadsheet',
+    'dbf'           => 'application/x-dbase',
+    'ilbm'          => 'image/x-ilbm',
+    'aw'            => 'application/x-applix-word',
+    'gb'            => 'application/x-gameboy-rom',
+    'xmi'           => 'text/x-xmi',
+    'abw.gz'        => 'application/x-abiword',
+    'XM'            => 'audio/x-mod',
+    'gnumeric'      => 'application/x-gnumeric',
+    'bak'           => 'application/x-trash',
+    'xslfo'         => 'text/x-xslfo',
+    'gg'            => 'application/x-sms-rom',
+    'cgi'           => 'application/x-cgi',
+    'mgp'           => 'application/x-magicpoint',
+    'spd'           => 'application/x-font-speedo',
+    'gnucash'       => 'application/x-gnucash',
+    'cls'           => 'text/x-tex',
+    'php4'          => 'application/x-php',
+    'sun'           => 'image/x-sun-raster',
+    'pyc'           => 'application/x-python-bytecode',
+    'xcf'           => 'image/x-xcf',
+    'xbel'          => 'application/x-xbel',
+    'jpr'           => 'application/x-jbuilder-project',
+    'afm'           => 'application/x-font-afm',
+    'fig'           => 'image/x-xfig',
+    'perl'          => 'application/x-perl',
+    'rej'           => 'application/x-reject',
+    'qtvr'          => 'video/quicktime',
+    'jpx'           => 'application/x-jbuilder-project',
+    '669'           => 'audio/x-mod',
+    'kdelnk'        => 'application/x-desktop',
+    'md'            => 'application/x-genesis-rom',
+    'pyo'           => 'application/x-python-bytecode',
+    'oleo'          => 'application/x-oleo',
+    'ac3'           => 'audio/ac3',
+    'mml'           => 'text/mathml',
+    'par2'          => 'application/x-par2',
+    'sylk'          => 'text/spreadsheet',
+    'C'             => 'text/x-c++src',
+    'cert'          => 'application/x-x509-ca-cert',
+    'ult'           => 'audio/x-mod',
+    'lwo'           => 'image/x-lwo',
+    'dcl'           => 'text/x-dcl',
+    'zoo'           => 'application/x-zoo',
+    'dcm'           => 'application/dicom',
+    'mm'            => 'text/x-troff-mm',
+    'iff'           => 'image/x-iff',
+    'lws'           => 'image/x-lws',
+    'zabw'          => 'application/x-abiword',
+    'blender'       => 'application/x-blender',
+    'glade'         => 'application/x-glade',
+    'rpm'           => 'application/x-rpm',
+    'tga'           => 'image/x-tga',
+    'jar'           => 'application/x-jar',
+    'cpio.gz'       => 'application/x-cpio-compressed',
+    'dsl'           => 'text/x-dsl',
+    'kra'           => 'application/x-krita',
+    'n64'           => 'application/x-n64-rom',
+    'm4a'           => 'audio/x-m4a',
+    'c++'           => 'text/x-c++src',
+    'moov'          => 'video/quicktime',
+    'sam'           => 'application/x-amipro',
+    'nsv'           => 'video/x-nsv',
+    'dia'           => 'application/x-dia-diagram',
+    'xi'            => 'audio/x-xi',
+    'hp'            => 'text/x-chdr',
+    'gen'           => 'application/x-genesis-rom',
+    'url'           => 'text/x-uri',
+    'hs'            => 'text/x-haskell',
+    'xm'            => 'audio/x-xm',
+    'sql'           => 'text/x-sql',
+    'NSV'           => 'video/x-nsv',
+    'desktop'       => 'application/x-desktop',
+    'mng'           => 'video/x-mng',
+    'pem'           => 'application/x-x509-ca-cert',
+    'slk'           => 'text/spreadsheet',
+    'cs'            => 'text/x-csharp',
+    'arj'           => 'application/x-arj',
+    'a'             => 'application/x-archive',
+    'lhs'           => 'text/x-literate-haskell',
+    'gcrd'          => 'text/directory',
+    'vct'           => 'text/directory',
+    'wk1'           => 'application/vnd.lotus-1-2-3',
+    'msx'           => 'application/x-msx-rom',
+    'sgi'           => 'image/x-sgi',
+    'd'             => 'text/x-dsrc',
+    'CSSL'          => 'text/css',
+    'wk3'           => 'application/vnd.lotus-1-2-3',
+    'abw'           => 'application/x-abiword',
+    'pict1'         => 'image/x-pict'
+);
diff --git a/framework/Mime/package.xml b/framework/Mime/package.xml
new file mode 100644 (file)
index 0000000..17b5255
--- /dev/null
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Mime</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde MIME Library</summary>
+ <description>The Horde_Mime:: class provides methods for dealing with MIME (RFC 2045) and related e-mail (RFC 822/2822/5322) standards.
+ </description>
+ <lead>
+  <name>Chuck Hagenbuch</name>
+  <user>chuck</user>
+  <email>chuck@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <lead>
+  <name>Michael Slusarz</name>
+  <user>slusarz</user>
+  <email>slusarz@horde.org</email>
+  <active>yes</active>
+ </lead>
+ <date>2008-10-29</date>
+ <time>14:00:00</time>
+ <version>
+  <release>0.1.0</release>
+  <api>0.1.0</api>
+ </version>
+ <stability>
+  <release>alpha</release>
+  <api>alpha</api>
+ </stability>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>* Initial package.</notes>
+ <contents>
+  <dir name="/">
+   <dir name="lib">
+    <dir name="Horde">
+     <dir name="Mime">
+      <dir name="Viewer">
+       <dir name="ooo">
+        <file name="common.xsl" role="php" />
+        <file name="global_document.xsl" role="php" />
+        <file name="main_html.xsl" role="php" />
+        <file name="palm.xsl" role="php" />
+        <file name="style_header.xsl" role="php" />
+        <file name="style_inlined.xsl" role="php" />
+        <file name="style_mapping.xsl" role="php" />
+        <file name="table.xsl" role="php" />
+        <file name="table_cells.xsl" role="php" />
+        <file name="table_columns.xsl" role="php" />
+        <file name="table_rows.xsl" role="php" />
+       </dir> <!-- /lib/Horde/Mime/Viewer/ooo -->
+       <file name="Driver.php" role="php" />
+       <file name="audio.php" role="php" />
+       <file name="css.php" role="php" />
+       <file name="deb.php" role="php" />
+       <file name="default.php" role="php" />
+       <file name="enriched.php" role="php" />
+       <file name="enscript.php" role="php" />
+       <file name="html.php" role="php" />
+       <file name="images.php" role="php" />
+       <file name="msexcel.php" role="php" />
+       <file name="mspowerpoint.php" role="php" />
+       <file name="msword.php" role="php" />
+       <file name="ooo.php" role="php" />
+       <file name="pdf.php" role="php" />
+       <file name="php.php" role="php" />
+       <file name="plain.php" role="php" />
+       <file name="rar.php" role="php" />
+       <file name="report.php" role="php" />
+       <file name="rfc822.php" role="php" />
+       <file name="richtext.php" role="php" />
+       <file name="rpm.php" role="php" />
+       <file name="rtf.php" role="php" />
+       <file name="security.php" role="php" />
+       <file name="simple.php" role="php" />
+       <file name="smil.php" role="php" />
+       <file name="source.php" role="php" />
+       <file name="srchighlite.php" role="php" />
+       <file name="tgz.php" role="php" />
+       <file name="tnef.php" role="php" />
+       <file name="vcard.php" role="php" />
+       <file name="webcpp.php" role="php" />
+       <file name="wordperfect.php" role="php" />
+       <file name="zip.php" role="php" />
+      </dir> <!-- /lib/Horde/Mime/Viewer -->
+      <file name="Address.php" role="php" />
+      <file name="Headers.php" role="php" />
+      <file name="Magic.php" role="php" />
+      <file name="Mail.php" role="php" />
+      <file name="Mdn.php" role="php" />
+      <file name="Part.php" role="php" />
+      <file name="Viewer.php" role="php" />
+      <file name="mime.magic.php" role="php" />
+      <file name="mime.mapping.php" role="php" />
+     </dir> <!-- /lib/Horde/Mime -->
+     <file name="Mime.php" role="php" />
+    </dir> <!-- /lib/Horde -->
+   </dir> <!-- /lib -->
+   <dir name="tests">
+    <file name="bug_325.phpt" role="test" />
+    <file name="mail_001.phpt" role="test" />
+    <file name="mail_002.phpt" role="test" />
+    <file name="mail_003.phpt" role="test" />
+    <file name="mail_004.phpt" role="test" />
+    <file name="mail_005.phpt" role="test" />
+    <file name="mail_dummy.inc" role="test" />
+   </dir> <!-- /tests -->
+  </dir> <!-- / -->
+ </contents>
+ <dependencies>
+  <required>
+   <php>
+    <min>5.2.0</min>
+   </php>
+   <pearinstaller>
+    <min>1.5.0</min>
+   </pearinstaller>
+   <package>
+    <name>Mail_mimeDecode</name>
+    <channel>pear.php.net</channel>
+   </package>
+   <package>
+    <name>Util</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <extension>
+    <name>gettext</name>
+   </extension>
+  </required>
+  <optional>
+   <package>
+    <name>Auth</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Horde_Browser</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Horde_Compress</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Horde_Framework</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>iCalendar</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Horde_Prefs</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Horde_SessionObjects</name>
+    <channel>pear.horde.org</channel>
+   </package>
+   <package>
+    <name>Horde_Text_Filter</name>
+    <channel>pear.horde.org</channel>
+   </package>
+  </optional>
+ </dependencies>
+ <phprelease>
+  <filelist>
+   <install name="lib/Horde/Mime/Viewer/ooo/common.xsl" as="Horde/Mime/Viewer/ooo/common.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/global_document.xsl" as="Horde/Mime/Viewer/ooo/global_document.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/main_html.xsl" as="Horde/Mime/Viewer/ooo/main_html.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/palm.xsl" as="Horde/Mime/Viewer/ooo/palm.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/style_header.xsl" as="Horde/Mime/Viewer/ooo/style_header.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/style_inlined.xsl" as="Horde/Mime/Viewer/ooo/style_inlined.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/style_mapping.xsl" as="Horde/Mime/Viewer/ooo/style_mapping.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/table.xsl" as="Horde/Mime/Viewer/ooo/table.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/table_cells.xsl" as="Horde/Mime/Viewer/ooo/table_cells.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/table_columns.xsl" as="Horde/Mime/Viewer/ooo/table_columns.xsl" />
+   <install name="lib/Horde/Mime/Viewer/ooo/table_rows.xsl" as="Horde/Mime/Viewer/ooo/table_rows.xsl" />
+   <install name="lib/Horde/Mime/Viewer/Driver.php" as="Horde/Mime/Viewer/Driver.php" />
+   <install name="lib/Horde/Mime/Viewer/audio.php" as="Horde/Mime/Viewer/audio.php" />
+   <install name="lib/Horde/Mime/Viewer/css.php" as="Horde/Mime/Viewer/css.php" />
+   <install name="lib/Horde/Mime/Viewer/deb.php" as="Horde/Mime/Viewer/deb.php" />
+   <install name="lib/Horde/Mime/Viewer/default.php" as="Horde/Mime/Viewer/default.php" />
+   <install name="lib/Horde/Mime/Viewer/enriched.php" as="Horde/Mime/Viewer/enriched.php" />
+   <install name="lib/Horde/Mime/Viewer/enscript.php" as="Horde/Mime/Viewer/enscript.php" />
+   <install name="lib/Horde/Mime/Viewer/html.php" as="Horde/Mime/Viewer/html.php" />
+   <install name="lib/Horde/Mime/Viewer/images.php" as="Horde/Mime/Viewer/images.php" />
+   <install name="lib/Horde/Mime/Viewer/msexcel.php" as="Horde/Mime/Viewer/msexcel.php" />
+   <install name="lib/Horde/Mime/Viewer/mspowerpoint.php" as="Horde/Mime/Viewer/mspowerpoint.php" />
+   <install name="lib/Horde/Mime/Viewer/msword.php" as="Horde/Mime/Viewer/msword.php" />
+   <install name="lib/Horde/Mime/Viewer/ooo.php" as="Horde/Mime/Viewer/ooo.php" />
+   <install name="lib/Horde/Mime/Viewer/pdf.php" as="Horde/Mime/Viewer/pdf.php" />
+   <install name="lib/Horde/Mime/Viewer/php.php" as="Horde/Mime/Viewer/php.php" />
+   <install name="lib/Horde/Mime/Viewer/plain.php" as="Horde/Mime/Viewer/plain.php" />
+   <install name="lib/Horde/Mime/Viewer/rar.php" as="Horde/Mime/Viewer/rar.php" />
+   <install name="lib/Horde/Mime/Viewer/report.php" as="Horde/Mime/Viewer/report.php" />
+   <install name="lib/Horde/Mime/Viewer/rfc822.php" as="Horde/Mime/Viewer/rfc822.php" />
+   <install name="lib/Horde/Mime/Viewer/richtext.php" as="Horde/Mime/Viewer/richtext.php" />
+   <install name="lib/Horde/Mime/Viewer/rpm.php" as="Horde/Mime/Viewer/rpm.php" />
+   <install name="lib/Horde/Mime/Viewer/rtf.php" as="Horde/Mime/Viewer/rtf.php" />
+   <install name="lib/Horde/Mime/Viewer/security.php" as="Horde/Mime/Viewer/security.php" />
+   <install name="lib/Horde/Mime/Viewer/simple.php" as="Horde/Mime/Viewer/simple.php" />
+   <install name="lib/Horde/Mime/Viewer/smil.php" as="Horde/Mime/Viewer/smil.php" />
+   <install name="lib/Horde/Mime/Viewer/source.php" as="Horde/Mime/Viewer/source.php" />
+   <install name="lib/Horde/Mime/Viewer/srchighlite.php" as="Horde/Mime/Viewer/srchighlite.php" />
+   <install name="lib/Horde/Mime/Viewer/tgz.php" as="Horde/Mime/Viewer/tgz.php" />
+   <install name="lib/Horde/Mime/Viewer/tnef.php" as="Horde/Mime/Viewer/tnef.php" />
+   <install name="lib/Horde/Mime/Viewer/vcard.php" as="Horde/Mime/Viewer/vcard.php" />
+   <install name="lib/Horde/Mime/Viewer/webcpp.php" as="Horde/Mime/Viewer/webcpp.php" />
+   <install name="lib/Horde/Mime/Viewer/wordperfect.php" as="Horde/Mime/Viewer/wordperfect.php" />
+   <install name="lib/Horde/Mime/Viewer/zip.php" as="Horde/Mime/Viewer/zip.php" />
+   <install name="lib/Horde/Mime/Address.php" as="Horde/Mime/Address.php" />
+   <install name="lib/Horde/Mime/Headers.php" as="Horde/Mime/Headers.php" />
+   <install name="lib/Horde/Mime/Mdn.php" as="Horde/Mime/Mdn.php" />
+   <install name="lib/Horde/Mime/Magic.php" as="Horde/Mime/Magic.php" />
+   <install name="lib/Horde/Mime/Mail.php" as="Horde/Mime/Mail.php" />
+   <install name="lib/Horde/Mime/Part.php" as="Horde/Mime/Part.php" />
+   <install name="lib/Horde/Mime/Viewer.php" as="Horde/Mime/Viewer.php" />
+   <install name="lib/Horde/Mime/mime.magic.php" as="Horde/Mime/mime.magic.php" />
+   <install name="lib/Horde/Mime/mime.mapping.php" as="Horde/Mime/mime.mapping.php" />
+   <install name="lib/Horde/Mime.php" as="Horde/Mime.php" />
+  </filelist>
+ </phprelease>
+</package>
diff --git a/framework/Mime/test/Horde/Mime/AllTests.php b/framework/Mime/test/Horde/Mime/AllTests.php
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/framework/Mime/test/Horde/Mime/attachment.bin b/framework/Mime/test/Horde/Mime/attachment.bin
new file mode 100644 (file)
index 0000000..a17f84e
--- /dev/null
@@ -0,0 +1 @@
+Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich.
diff --git a/framework/Mime/test/Horde/Mime/bug_325.phpt b/framework/Mime/test/Horde/Mime/bug_325.phpt
new file mode 100644 (file)
index 0000000..16fbaf6
--- /dev/null
@@ -0,0 +1,12 @@
+--TEST--
+Bug #338 (fileinfo returning charset)
+--SKIPIF--
+<?php if (!extension_loaded('fileinfo')) echo 'skip'; ?>
+--FILE--
+<?php
+require_once 'Horde/Util.php';
+require dirname(__FILE__) . '/../lib/Horde/MIME/Magic.php';
+echo MIME_Magic::analyzeFile(dirname(__FILE__) . '/bug_325.txt');
+?>
+--EXPECT--
+text/plain
diff --git a/framework/Mime/test/Horde/Mime/bug_325.txt b/framework/Mime/test/Horde/Mime/bug_325.txt
new file mode 100644 (file)
index 0000000..0dc0fba
--- /dev/null
@@ -0,0 +1,6 @@
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
+tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
+quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
+proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
diff --git a/framework/Mime/test/Horde/Mime/bug_4834.phpt b/framework/Mime/test/Horde/Mime/bug_4834.phpt
new file mode 100644 (file)
index 0000000..6230ff5
--- /dev/null
@@ -0,0 +1,11 @@
+--TEST--
+Bug #4834 Wrong encoding of email lists with groups.
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/../lib/Horde/MIME.php';
+echo MIME::encodeAddress('"John Doe" <john@example.com>, Group: peter@example.com, jane@example.com;');
+
+?>
+--EXPECT--
+John Doe <john@example.com>, Group: peter@example.com, jane@example.com;
diff --git a/framework/Mime/test/Horde/Mime/bug_6896.phpt b/framework/Mime/test/Horde/Mime/bug_6896.phpt
new file mode 100644 (file)
index 0000000..ca2e024
--- /dev/null
@@ -0,0 +1,16 @@
+--TEST--
+Bug #6896 MIME::rfc822Explode parsing broken
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/../MIME.php';
+var_dump(MIME::rfc822Explode('addr1@example.com, addr2@example.com'));
+
+?>
+--EXPECT--
+array(2) {
+  [0]=>
+  string(17) "addr1@example.com"
+  [1]=>
+  string(18) " addr2@example.com"
+}
diff --git a/framework/Mime/test/Horde/Mime/contents.phpt b/framework/Mime/test/Horde/Mime/contents.phpt
new file mode 100644 (file)
index 0000000..809f774
--- /dev/null
@@ -0,0 +1,67 @@
+--TEST--
+MIME_Contents tests.
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/../MIME/Contents.php';
+
+$_SERVER['SERVER_NAME'] = 'mail.example.com';
+$message = MIME_Structure::parseTextMIMEMessage(
+    file_get_contents(dirname(__FILE__) . '/contents1.eml'));
+$contents = new MIME_Contents($message);
+
+var_export($contents->getDownloadAllList());
+echo "\n";
+var_export($contents->getAttachmentContents());
+
+?>
+--EXPECT--
+array (
+  0 => '2.0',
+)
+array (
+  0 => 
+  array (
+    'name' => 'Weitergeleitete Nachricht: Small message',
+    'data' => 'Return-Path: <jan@horde.org>
+Received: from neo.wg.de ([unix socket])
+        by neo (Cyrus v2.2.13) with LMTPA;
+        Tue, 11 Mar 2008 17:26:11 +0100
+X-Sieve: CMU Sieve 2.2
+Received: from localhost (localhost [127.0.0.1])
+       by neo.wg.de (Postfix) with ESMTP id 142BF32B032
+       for <jan@localhost.wg.de>; Tue, 11 Mar 2008 17:26:11 +0100 (CET)
+Received: from neo.wg.de ([127.0.0.1])
+ by localhost (neo.wg.de [127.0.0.1]) (amavisd-new, port 10024) with ESMTP
+ id 02540-02 for <jan@localhost.wg.de>; Tue, 11 Mar 2008 17:26:02 +0100 (CET)
+Received: from localhost (localhost [127.0.0.1])
+       by neo.wg.de (Postfix) with ESMTP id 21E2532B037
+       for <jan@localhost>; Tue, 11 Mar 2008 17:26:02 +0100 (CET)
+Received: from 192.168.60.101 ([192.168.60.101]) by neo.wg.de (Horde
+       Framework) with HTTP; Tue, 11 Mar 2008 17:26:02 +0100
+Message-ID: <20080311172602.12293hbhf6ddsza0@neo.wg.de>
+X-Priority: 3 (Normal)
+Date: Tue, 11 Mar 2008 17:26:02 +0100
+From: Jan Schneider <jan@horde.org>
+To: "jan@localhost" <jan@wg.de>
+Subject: Small message
+MIME-Version: 1.0
+Content-Type: text/plain;
+       charset=ISO-8859-1;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+User-Agent: Internet Messaging Program (IMP) H3 (5.0-cvs)
+X-Virus-Scanned: amavisd-new at wg.de
+X-Spam-Status: No, score=-4.351 required=5 tests=[ALL_TRUSTED=-1.8, AWL=0.048,
+ BAYES_00=-2.599]
+X-Spam-Score: -4.351
+X-Spam-Level: 
+
+Small message text.
+
+
+',
+  ),
+)
diff --git a/framework/Mime/test/Horde/Mime/mail_001.phpt b/framework/Mime/test/Horde/Mime/mail_001.phpt
new file mode 100644 (file)
index 0000000..dfd7cfc
--- /dev/null
@@ -0,0 +1,30 @@
+--TEST--
+MIME_Mail constructor test
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/mail_dummy.inc';
+
+$mail = new MIME_Mail('My Subject', "This is\nthe body",
+                      'recipient@example.com', 'sender@example.com',
+                      'iso-8859-15');
+echo $mail->send('dummy');
+
+?>
+--EXPECTF--
+Subject: My Subject
+To: recipient@example.com
+From: sender@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: text/plain;
+       charset=iso-8859-15;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+This is
+the body
diff --git a/framework/Mime/test/Horde/Mime/mail_002.phpt b/framework/Mime/test/Horde/Mime/mail_002.phpt
new file mode 100644 (file)
index 0000000..28d1d9d
--- /dev/null
@@ -0,0 +1,36 @@
+--TEST--
+MIME_Mail methods test
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/mail_dummy.inc';
+
+$mail = new MIME_Mail();
+$mail->addHeader('Subject', 'My Subject');
+$mail->setBody("This is\nthe body", 'iso-8859-15');
+$mail->addHeader('To', 'recipient@example.com');
+$mail->addHeader('Cc', 'null@example.com');
+$mail->addHeader('Bcc', 'invisible@example.com');
+$mail->addHeader('From', 'sender@example.com');
+$mail->removeHeader('Cc');
+
+echo $mail->send('dummy');
+
+?>
+--EXPECTF--
+Subject: My Subject
+To: recipient@example.com
+From: sender@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: text/plain;
+       charset=iso-8859-15;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+This is
+the body
diff --git a/framework/Mime/test/Horde/Mime/mail_003.phpt b/framework/Mime/test/Horde/Mime/mail_003.phpt
new file mode 100644 (file)
index 0000000..a0a3ef1
--- /dev/null
@@ -0,0 +1,33 @@
+--TEST--
+MIME_Mail encoding test
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/mail_dummy.inc';
+require 'Horde/NLS.php';
+
+$mail = new MIME_Mail('Schöner Betreff', "Hübsche Umlaute \n und Leerzeichen.",
+                      'Empfänger <recipient@example.com>',
+                      'sender@example.com', 'iso-8859-1');
+$mail->addHeader('Cc', 'Der schöne Peter <peter@example.com>', 'iso-8859-15');
+echo $mail->send('dummy');
+
+?>
+--EXPECTF--
+Subject: =?iso-8859-1?b?U2No9m5lcg==?= Betreff
+To: =?iso-8859-1?b?RW1wZuRuZ2Vy?= <recipient@example.com>
+From: sender@example.com
+Cc: Der =?iso-8859-15?b?c2No9m5l?= Peter <peter@example.com>
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: text/plain;
+       charset=iso-8859-1;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: quoted-printable
+
+H=FCbsche Umlaute
+  und Leerzeichen.
diff --git a/framework/Mime/test/Horde/Mime/mail_004.phpt b/framework/Mime/test/Horde/Mime/mail_004.phpt
new file mode 100644 (file)
index 0000000..23fd743
--- /dev/null
@@ -0,0 +1,59 @@
+--TEST--
+MIME_Mail::addPart() test
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/mail_dummy.inc';
+
+$mail = new MIME_Mail('My Subject', "This is\nthe body",
+                      'recipient@example.com', 'sender@example.com',
+                      'iso-8859-15');
+$mail->addPart('text/plain', 'This is a plain text', 'iso-8859-1', 'inline');
+$mail->addPart('application/octet-stream',
+               file_get_contents(dirname(__FILE__) . '/attachment.bin'),
+               null, 'attachment');
+
+echo $mail->send('dummy');
+
+?>
+--EXPECTF--
+Subject: My Subject
+To: recipient@example.com
+From: sender@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+       boundary="=_%s"
+Content-Transfer-Encoding: 7bit
+
+This message is in MIME format.
+
+--=_%s
+Content-Type: text/plain;
+       charset=iso-8859-15;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+This is
+the body
+
+--=_%s
+Content-Type: text/plain;
+       charset=iso-8859-1
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+This is a plain text
+--=_%s
+Content-Type: application/octet-stream
+Content-Disposition: attachment
+Content-Transfer-Encoding: base64
+
+WnfDtmxmIEJveGvDpG1wZmVyIGphZ2VuIFZpa3RvciBxdWVyIMO8YmVyIGRlbiBncm/Dn2VuIFN5
+bHRlciBEZWljaC4K
+
+--=_%s--
diff --git a/framework/Mime/test/Horde/Mime/mail_005.phpt b/framework/Mime/test/Horde/Mime/mail_005.phpt
new file mode 100644 (file)
index 0000000..5683ec4
--- /dev/null
@@ -0,0 +1,100 @@
+--TEST--
+MIME_Mail HTML test
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/mail_dummy.inc';
+
+$mail = new MIME_Mail('My Subject', null, 'recipient@example.com',
+                      'sender@example.com');
+$mail->setBody("This is\nthe plain text body.");
+echo $mail->send('dummy');
+
+echo "====================================================================\n";
+
+$mail = new MIME_Mail('My Subject', null, 'recipient@example.com',
+                      'sender@example.com');
+$mail->setHTMLBody("<h1>Header Title</h1>\n<p>This is<br />the html text body.</p>",
+                   'iso-8859-1', false);
+echo $mail->send('dummy');
+
+echo "====================================================================\n";
+
+$mail = new MIME_Mail('My Subject', null, 'recipient@example.com',
+                      'sender@example.com');
+$mail->setHTMLBody("<h1>Header Title</h1>\n<p>This is<br />the html text body.</p>");
+echo $mail->send('dummy');
+
+?>
+--EXPECTF--
+Subject: My Subject
+To: recipient@example.com
+From: sender@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: text/plain;
+       charset=iso-8859-1;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+This is
+the plain text body.
+====================================================================
+Subject: My Subject
+To: recipient@example.com
+From: sender@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: text/html;
+       charset=iso-8859-1
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+<h1>Header Title</h1>
+<p>This is<br />the html text body.</p>
+====================================================================
+Subject: My Subject
+To: recipient@example.com
+From: sender@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: multipart/alternative;
+       boundary="=_%s"
+Content-Transfer-Encoding: 7bit
+
+This message is in MIME format.
+
+--=_%s
+Content-Type: text/plain;
+       charset=iso-8859-1;
+       DelSp="Yes";
+       format="flowed"
+Content-Description: Plaintext Version of Message
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+
+
+HEADER TITLE
+
+This is
+the html text body.
+
+--=_%s
+Content-Type: text/html;
+       charset=iso-8859-1
+Content-Description: HTML Version of Message
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+<h1>Header Title</h1>
+<p>This is<br />the html text body.</p>
+--=_%s--
diff --git a/framework/Mime/test/Horde/Mime/mail_006.phpt b/framework/Mime/test/Horde/Mime/mail_006.phpt
new file mode 100644 (file)
index 0000000..8bed446
--- /dev/null
@@ -0,0 +1,76 @@
+--TEST--
+MIME_Mail::addAttachment() test
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/mail_dummy.inc';
+
+$mail = new MIME_Mail('My Subject', "This is\nthe body",
+                      'recipient@example.com', 'sender@example.com',
+                      'iso-8859-15');
+$mail->addAttachment(dirname(__FILE__) . '/attachment.bin');
+$mail->addAttachment(dirname(__FILE__) . '/mail_dummy.inc', 'my_name.html', 'text/html', 'iso-8859-15');
+
+echo $mail->send('dummy');
+
+?>
+--EXPECTF--
+Subject: My Subject
+To: recipient@example.com
+From: sender@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: multipart/mixed;
+       boundary="=_%s"
+Content-Transfer-Encoding: 7bit
+
+This message is in MIME format.
+
+--=_%s
+Content-Type: text/plain;
+       charset=iso-8859-15;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+This is
+the body
+
+--=_%s
+Content-Type: application/octet-stream;
+       name="attachment.bin"
+Content-Disposition: attachment;
+       filename="attachment.bin"
+Content-Transfer-Encoding: base64
+
+WnfDtmxmIEJveGvDpG1wZmVyIGphZ2VuIFZpa3RvciBxdWVyIMO8YmVyIGRlbiBncm/Dn2VuIFN5
+bHRlciBEZWljaC4K
+
+--=_%s
+Content-Type: text/html;
+       charset=iso-8859-15;
+       name="my_name.html"
+Content-Disposition: attachment;
+       filename="my_name.html"
+Content-Transfer-Encoding: 7bit
+
+<?php
+/**
+ * @package Mail
+ */
+
+require dirname(__FILE__) . '/../MIME/Mail.php';
+$_SERVER['SERVER_NAME'] = 'mail.example.com';
+
+class Mail_dummy extends Mail {
+    function send($recipients, $headers, $body)
+    {
+        list(,$text_headers) = Mail::prepareHeaders($headers);
+        return $text_headers . "\n\n" . $body;
+    }
+}
+
+--=_%s--
diff --git a/framework/Mime/test/Horde/Mime/mail_007.phpt b/framework/Mime/test/Horde/Mime/mail_007.phpt
new file mode 100644 (file)
index 0000000..5deb030
--- /dev/null
@@ -0,0 +1,61 @@
+--TEST--
+MIME_Mail reusing test
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/mail_dummy.inc';
+
+$mail = new MIME_Mail('My Subject', "This is\nthe body",
+                      'recipient@example.com', 'sender@example.com',
+                      'iso-8859-15');
+echo $mail->send('dummy');
+$id = $mail->_headers->getValue('message-id');
+
+echo "====================================================================\n";
+
+$mail->addHeader('To', 'Änderung <other@example.com>', 'utf-8');
+echo $mail->send('dummy');
+
+echo "====================================================================\n";
+
+var_dump($id != $mail->_headers->getValue('message-id'));
+
+?>
+--EXPECTF--
+Subject: My Subject
+To: recipient@example.com
+From: sender@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: text/plain;
+       charset=iso-8859-15;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+This is
+the body
+====================================================================
+Subject: My Subject
+From: sender@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: text/plain;
+       charset=iso-8859-15;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+To: =?utf-8?b?w4RuZGVydW5n?= <other@example.com>
+
+This is
+the body
+
+====================================================================
+bool(true)
+
diff --git a/framework/Mime/test/Horde/Mime/mail_008.phpt b/framework/Mime/test/Horde/Mime/mail_008.phpt
new file mode 100644 (file)
index 0000000..a086b37
--- /dev/null
@@ -0,0 +1,39 @@
+--TEST--
+MIME_Mail flowed text test
+--FILE--
+<?php
+
+require dirname(__FILE__) . '/mail_dummy.inc';
+
+$mail = new MIME_Mail();
+$mail->addHeader('Subject', 'My Subject');
+$mail->addHeader('To', 'recipient@example.com');
+$mail->setBody('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.');
+
+echo $mail->send('dummy');
+
+?>
+--EXPECTF--
+Subject: My Subject
+To: recipient@example.com
+Message-ID: <%d.%s@mail.example.com>
+User-Agent: Horde Application Framework 3.2
+Date: %s, %d %s %d %d:%d:%d %s%d
+MIME-Version: 1.0
+Content-Type: text/plain;
+       charset=iso-8859-1;
+       DelSp="Yes";
+       format="flowed"
+Content-Disposition: inline
+Content-Transfer-Encoding: 7bit
+
+Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do  
+eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad  
+minim veniam, quis nostrud exercitation ullamco laboris nisi ut  
+aliquip ex ea commodo
+consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
+cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat  
+cupidatat non proident, sunt in culpa qui officia deserunt mollit anim  
+id est laborum.
\ No newline at end of file
diff --git a/framework/Mime/test/Horde/Mime/mail_dummy.inc b/framework/Mime/test/Horde/Mime/mail_dummy.inc
new file mode 100644 (file)
index 0000000..b323499
--- /dev/null
@@ -0,0 +1,15 @@
+<?php
+/**
+ * @package Mail
+ */
+
+require dirname(__FILE__) . '/../MIME/Mail.php';
+$_SERVER['SERVER_NAME'] = 'mail.example.com';
+
+class Mail_dummy extends Mail {
+    function send($recipients, $headers, $body)
+    {
+        list(,$text_headers) = Mail::prepareHeaders($headers);
+        return $text_headers . "\n\n" . $body;
+    }
+}
diff --git a/framework/Mime/test/Horde/Mime/url.phpt b/framework/Mime/test/Horde/Mime/url.phpt
new file mode 100644 (file)
index 0000000..d872762
--- /dev/null
@@ -0,0 +1,48 @@
+--TEST--
+MIME_Viewer_html: URL dereferer tests
+--FILE--
+<?php
+
+define('HORDE_BASE', dirname(__FILE__) . '/../../..');
+require_once dirname(__FILE__) . '/../MIME/Viewer.php';
+require_once dirname(__FILE__) . '/../MIME/Viewer/html.php';
+require_once 'Horde.php';
+
+class Registry {
+    function get($param, $app = null)
+    {
+        if ($param == 'webroot' || $app == 'horde') {
+            return '/horde';
+        }
+        die("Can't emulate Registry. \$param: $param, \$app: $app");
+    }
+}
+
+class Browser {
+    function isBrowser($agent)
+    {
+        return $agent == 'msie';
+    }
+}
+
+$conf['server']['name'] = 'www.example.com';
+$conf['server']['port'] = 80;
+$conf['use_ssl'] = 0;
+$registry = new Registry();
+$browser = new Browser();
+$viewer = new MIME_Viewer_html($null);
+
+for ($i = 1; $i <= 7; $i++) {
+    $data = file_get_contents(dirname(__FILE__) . '/url' . $i . '.html');
+    echo $viewer->_cleanHTML($data);
+}
+
+?>
+--EXPECT--
+<A href="http://www.example.com/horde/services/go.php?url=http%3A%2F%2F66.102.7.147%2F">link</A>
+<A href="http://www.example.com/horde/services/go.php?url=http%3A%2F%2F%2577%2577%2577%252E%2567%256F%256F%2567%256C%2565%252E%2563%256F%256D">link</A>
+<A href="http://www.example.com/horde/services/go.php?url=ht%3A%2F%2Fwww.google.com%2F">link</A>
+<A href="http://www.example.com/horde/services/go.php?url=http%3A%2F%2Fgoogle.com%2F">link</A>
+<A href="http://www.example.com/horde/services/go.php?url=http%3A%2F%2Fwww.google.com.%2F">link</A>
+<A href="http://www.example.com/horde/services/go.php?url=XSSCleaneddocument.location%3D%27http%3A%2F%2Fwww.google.com%2F%27">link</A>
+<A href="http://www.example.com/horde/services/go.php?url=http%3A%2F%2Fwww.gohttp%3A%2F%2Fwww.google.com%2Fogle.com%2F">link</A>
diff --git a/framework/Mime/test/Horde/Mime/url1.html b/framework/Mime/test/Horde/Mime/url1.html
new file mode 100644 (file)
index 0000000..0c88c14
--- /dev/null
@@ -0,0 +1 @@
+<A HREF=http://66.102.7.147/>link</A>
diff --git a/framework/Mime/test/Horde/Mime/url2.html b/framework/Mime/test/Horde/Mime/url2.html
new file mode 100644 (file)
index 0000000..94c45a2
--- /dev/null
@@ -0,0 +1 @@
+<A HREF=http://%77%77%77%2E%67%6F%6F%67%6C%65%2E%63%6F%6D>link</A>
diff --git a/framework/Mime/test/Horde/Mime/url3.html b/framework/Mime/test/Horde/Mime/url3.html
new file mode 100644 (file)
index 0000000..4c7eafe
--- /dev/null
@@ -0,0 +1 @@
+<A HREF=ht://www.google.com/>link</A>
diff --git a/framework/Mime/test/Horde/Mime/url4.html b/framework/Mime/test/Horde/Mime/url4.html
new file mode 100644 (file)
index 0000000..9356125
--- /dev/null
@@ -0,0 +1 @@
+<A HREF=http://google.com/>link</A>
diff --git a/framework/Mime/test/Horde/Mime/url5.html b/framework/Mime/test/Horde/Mime/url5.html
new file mode 100644 (file)
index 0000000..89dfbc6
--- /dev/null
@@ -0,0 +1 @@
+<A HREF=http://www.google.com./>link</A>
diff --git a/framework/Mime/test/Horde/Mime/url6.html b/framework/Mime/test/Horde/Mime/url6.html
new file mode 100644 (file)
index 0000000..73ad9fc
--- /dev/null
@@ -0,0 +1 @@
+<A HREF="javascript:document.location='http://www.google.com/'">link</A>
diff --git a/framework/Mime/test/Horde/Mime/url7.html b/framework/Mime/test/Horde/Mime/url7.html
new file mode 100644 (file)
index 0000000..cb5c800
--- /dev/null
@@ -0,0 +1 @@
+<A HREF=http://www.gohttp://www.google.com/ogle.com/>link</A>
diff --git a/framework/Mime/test/Horde/Mime/viewer_php.phpt b/framework/Mime/test/Horde/Mime/viewer_php.phpt
new file mode 100644 (file)
index 0000000..cceaa91
--- /dev/null
@@ -0,0 +1,21 @@
+--TEST--
+PHP source viewer
+--FILE--
+<?php
+
+require_once 'Horde/MIME/Viewer.php';
+require_once 'Horde/MIME/Viewer/php.php';
+
+$viewer = new MIME_Viewer_php($null);
+
+ini_set('highlight.comment', 'comment');
+ini_set('highlight.default', 'default');
+ini_set('highlight.keyword', 'keyword');
+ini_set('highlight.string', 'string');
+ini_set('highlight.html', 'html');
+echo $viewer->lineNumber(str_replace('&lt;?php&nbsp;', '', highlight_string('<?php highlight_file(__FILE__);', true)));
+?>
+--EXPECT--
+<ol class="code-listing striped">
+<li id="l1"><span class="default">highlight_file</span><span class="keyword">(</span><span class="default">__FILE__</span><span class="keyword">);</span></li>
+</ol>
\ No newline at end of file