blob: 42fc65749da9319e2779fd11927d6b9735eac009 [file] [log] [blame]
<?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//
* @copyright Copyright (C) 2005-2010 eZ Systems AS. All rights reserved.
* @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();
}
?>