<?php
/**
 * File containing the ezcMailRfc2231Implementation class
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 * @package Mail
 * @version //autogen//
 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 * @access private
 */

/**
 * This class parses header fields that conform to RFC2231.
 *
 * Headers conforming to this specification are Content-Type and Content-Disposition.
 *
 * @package Mail
 * @version //autogen//
 * @access private
 */
class ezcMailRfc2231Implementation
{
    /**
     * Returns the parsed header $header according to RFC 2231.
     *
     * This method returns the parsed header as a structured array and is
     * intended for internal usage. Use parseContentDisposition and
     * parseContentType to retrieve the correct header structs directly.
     *
     * @param string $header
     * @return array( 'argument', array( 'paramName' => array( value => string, charset => string,
     * language => string ) ) );
     */
    public static function parseHeader( $header )
    {
        $result = array();
        // argument
        if ( preg_match( '/^\s*([^;]*);?/i', $header, $matches ) )
        {
            $result[0] = $matches[1];
        }

        // We must go through all parameters and store this data because
        // parameters can be unordered. We will store them in this buffer
        // array( paramName => array( array( value => string, encoding ) ) )
        $parameterBuffer = array();

        // parameters
        if ( preg_match_all( '/\s*(\S*?)=\s?"?([^;"]*);?/i', $header, $matches, PREG_SET_ORDER ) )
        {
            foreach ( $matches as $parameter )
            {
                // if normal parameter, simply add it
                if ( !preg_match( '/([^\*]+)\*(\d+)?(\*)?/', $parameter[1], $metaData ) )
                {
                    $result[1][$parameter[1]] = array( 'value' => $parameter[2] );
                }
                else // coded and/or folded
                {
                    // metaData [1] holds the param name
                    // metaData [2] holds the count or is not set in case of charset only
                    // metaData [3] holds '*' if there is charset in addition to folding
                    if ( isset( $metaData[2] ) ) // we have folding
                    {
                        $parameterBuffer[$metaData[1]][$metaData[2]]['value'] = $parameter[2];
                        $parameterBuffer[$metaData[1]][$metaData[2]]['encoding'] =
                            isset( $metaData[3] ) ? true : false;;
                    }
                    else
                    {
                        $parameterBuffer[$metaData[1]][0]['value'] = $parameter[2];
                        $parameterBuffer[$metaData[1]][0]['encoding'] = true;
                    }
                }
            }

            // whohooo... we have all the parameters nicely sorted.
            // Now we must go through them all and convert them into the end result
            foreach ( $parameterBuffer as $paramName => $parts )
            {
                // fetch language and encoding if we have it
                // syntax: '[charset]'[language]'encoded_string
                $language = null;
                $charset = null;
                if ( $parts[0]['encoding'] == true )
                {
                    preg_match( "/(\S*)'(\S*)'(.*)/", $parts[0]['value'], $matches );
                    $charset = $matches[1];
                    $language = $matches[2];
                    $parts[0]['value'] = urldecode( $matches[3] ); // rewrite value: todo: decoding
                    $result[1][$paramName] = array( 'value' => $parts[0]['value'] );
                }

                $result[1][$paramName] = array( 'value' => $parts[0]['value'] );
                if ( $charset && strlen( $charset ) > 0 )
                {
                    $result[1][$paramName]['charset'] = $charset;
                }
                if ( $language && strlen( $language ) > 0 )
                {
                    $result[1][$paramName]['language'] = $language;
                }

                if ( count( $parts ) > 1 )
                {
                    for ( $i = 1; $i < count( $parts ); $i++ )
                    {
                        $result[1][$paramName]['value'] .= $parts[$i]['encoding'] ?
                            urldecode( $parts[$i]['value'] ) : $parts[$i]['value'];
                    }
                }
            }
        }
        return $result;
    }

    /**
     * Returns the a ezcMailContentDispositionHeader for the parsed $header.
     *
     * If $cd is provided this object will be used to fill in the blanks. This function
     * will not clear out any old values in the object.
     *
     * @param string $header
     * @param ezcMailContentDispositionHeader|null $cd
     * @return ezcMailContentDispositionHeader
     */
    public static function parseContentDisposition( $header, ?ezcMailContentDispositionHeader $cd = null )
    {
        if ( $cd === null )
        {
            $cd = new ezcMailContentDispositionHeader();
        }

        $parsedHeader = self::parseHeader( $header );
        $cd->disposition = $parsedHeader[0];
        if ( isset( $parsedHeader[1] ) )
        {
            foreach ( $parsedHeader[1] as $paramName => $data )
            {
                switch ( $paramName )
                {
                    case 'filename':
                        $cd->fileName = $data['value'];
                        $cd->displayFileName = trim( $data['value'], '"' );
                        if ( isset( $data['charset'] ) )
                        {
                            $cd->fileNameCharSet = $data['charset'];
                            $cd->displayFileName = ezcMailCharsetConverter::convertToUTF8Iconv( $cd->displayFileName, $cd->fileNameCharSet );
                        }
                        // Work around for bogus email clients that think
                        // it's allowed to use mime-encoding for filenames.
                        // It isn't, see RFC 2184, and issue #13038.
                        else if ( preg_match( '@^=\?[^?]+\?[QqBb]\?@', $cd->displayFileName ) )
                        {
                            $cd->displayFileName = ezcMailTools::mimeDecode( $cd->displayFileName );
                        }

                        if ( isset( $data['language'] ) )
                        {
                            $cd->fileNameLanguage = $data['language'];
                        }
                        break;
                    case 'creation-date':
                        $cd->creationDate = $data['value'];
                        break;
                    case 'modification-date':
                        $cd->modificationDate = $data['value'];
                        break;
                    case 'read-date':
                        $cd->readDate = $data['value'];
                        break;
                    case 'size':
                        $cd->size = $data['value'];
                        break;
                    default:
                        $cd->additionalParameters[$paramName] = $data['value'];
                        if ( isset( $data['charset'] ) )
                        {
                            $cd->additionalParametersMetaData[$paramName]['charSet'] = $data['charset'];
                        }
                        if ( isset( $data['language'] ) )
                        {
                            $cd->additionalParametersMetaData[$paramName]['language'] = $data['language'];
                        }
                        break;
                }
            }
        }
        return $cd;
    }
}
?>
