<?php
/**
 * File containing the ezcMailMultipartParser 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
 */

/**
 * Base class for Multipart parsers.
 *
 * @package Mail
 * @version //autogen//
 * @access private
 */
abstract class ezcMailMultipartParser extends ezcMailPartParser
{
    /**
     * The boundary separator string.
     *
     * @var string
     */
    private $boundary = null;

    /**
     * The headers for the multipart.
     *
     * @var ezcMailHeadersHolder
     */
    protected $headers = null;

    /**
     * The headers for the current subpart.
     *
     * @var ezcMailHeadersHolder
     */
    private $currentPartHeaders = null;

    /**
     * The current part.
     *
     * @var ezcMailPartParser
     */
    private $currentPartParser = null;

    /**
     * This state is used prior to hitting the first part.
     */
    const PARSE_STATE_PRE_FIRST = 1;

    /**
     * This state is used when the parser is parsing headers.
     */
    const PARSE_STATE_HEADERS = 2;

    /**
     * This state is used when the parser is parsing the body.
     */
    const PARSE_STATE_BODY = 3;

    /**
     * This state is set after the last of the parts is closed.
     */
    const PARSE_STATE_POST_LAST = 4;

    /**
     * Stores the state of the parser.
     *
     * @var int
     */
    private $parserState = self::PARSE_STATE_PRE_FIRST;

    /**
     * Constructs a new Multipart parser.
     *
     * @param ezcMailHeadersHolder $headers
     */
    public function __construct(ezcMailHeadersHolder $headers)
    {
        $this->headers = $headers;

        // get the boundary
        preg_match('/\s*boundary="?([^;"]*);?/i',
            $this->headers['Content-Type'],
            $parameters);
        if (count($parameters) > 0) {
            $this->boundary = trim($parameters[1], '"');
        } else {
            // no boundary?!? Houston, we have a problem.
            // todo: try to detect the boundary by scanning for --lines
        }
    }

    /**
     * Parses a multipart body.
     *
     * @throws ezcBaseFileNotFoundException
     *         if a neccessary temporary file could not be opened.
     * @param string $origLine
     */
    public function parseBody($origLine)
    {
        if ($this->parserState == self::PARSE_STATE_POST_LAST) {
            return;
        }

        $line = rtrim($origLine, "\r\n");

        // check if we hit any of the boundaries
        $newPart = false;
        $endOfMultipart = false;
        if (strlen($line) > 0 && $line[0] == "-") {
            if (strcmp(trim($line), '--' . $this->boundary) === 0) {
                $newPart = true;
            } else if (strcmp(trim($line), '--' . $this->boundary . '--') === 0) {
                $endOfMultipart = true;
            }
        }

        // actions to do when starting or finishing a part
        if ($newPart || $endOfMultipart) {
            if ($this->parserState != self::PARSE_STATE_BODY) {
                // something is b0rked, we got a new separator before getting a body
                // we'll skip this part and continue to the next
                $this->currentPartParser = null;
                $this->currentPartHeaders = new ezcMailHeadersHolder();
                $this->parserState = $newPart ? self::PARSE_STATE_HEADERS : self::PARSE_STATE_POST_LAST;
            } else {
                // complete the work on the current part if there was any
                if ($this->currentPartParser !== null) {
                    $part = $this->currentPartParser->finish();
                    if ($part !== null) // parsing failed
                    {
                        $this->partDone($part);
                    }
                }

                // prepare for a new part if any
                $this->currentPartParser = null;
                $this->parserState = self::PARSE_STATE_POST_LAST;
                if ($newPart) {
                    $this->parserState = self::PARSE_STATE_HEADERS;
                    $this->currentPartHeaders = new ezcMailHeadersHolder();
                }
            }
        } // normal data, pass to headers or current body
        else {
            if ($this->parserState == self::PARSE_STATE_HEADERS && $line == '') {
                $this->currentPartParser = self::createPartParserForHeaders($this->currentPartHeaders);
                $this->parserState = self::PARSE_STATE_BODY;
            } else if ($this->parserState == self::PARSE_STATE_HEADERS) {
                $this->parseHeader($line, $this->currentPartHeaders);
            } else if ($this->parserState == self::PARSE_STATE_BODY) {
                if ($this->currentPartParser) // we may have none if the part type was unknown
                {
                    // send body data to the part
                    $this->currentPartParser->parseBody($origLine);
                }
            }
            // we are done parsing the multipart, ignore anything else pushed to us.
        }
    }

    /**
     * Completes the parsing of the multipart and returns the corresponding part.
     *
     * This method should not be overriden. Use finishMultipart() instead.
     *
     * @return ezcMailMultipart
     */
    public function finish()
    {
        if ($this->parserState != self::PARSE_STATE_POST_LAST) {
            // this should never happen
            // let's give the last parser a chance to clean up after himself
            if ($this->currentPartParser !== null) {
                $part = $this->currentPartParser->finish();
                $this->partDone($part);
                $this->currentPartParser = null;
            }
        }
        $multipart = $this->finishMultipart();
        ezcMailPartParser::parsePartHeaders($this->headers, $multipart);
        $multipart->boundary = $this->boundary;
        return $multipart;
    }

    /**
     * This function will be called every time a part has been parsed.
     *
     * Implementors should put the part into the correct multitype part.
     * @param ezcMailPart $part
     */
    abstract public function partDone(ezcMailPart $part);

    /**
     * Returns the multipart part corresponding to the parsed object.
     *
     * This method is called by finish() when all parts have been parsed.
     *
     * @return ezcMailMultipart
     */
    abstract public function finishMultipart();
}

?>
