<?php 
 
namespace Riverline\MultiPartParser; 
 
/** 
 * Class Part 
 * @package Riverline\MultiPartParser 
 */ 
class Part 
{ 
    /** 
     * @var array 
     */ 
    protected $headers; 
 
    /** 
     * @var string 
     */ 
    protected $body; 
 
    /** 
     * @var Part[] 
     */ 
    protected $parts = array(); 
 
    /** 
     * @var bool 
     */ 
    protected $multipart = false; 
 
    /** 
     * MultiPart constructor. 
     * @param string $content 
     * @throws \InvalidArgumentException 
     */ 
    public function __construct($content) 
    { 
        // Split headers and body 
        $splits = preg_split('/(\r?\n){2}/', $content, 2); 
 
        if (count($splits) < 2) { 
            throw new \InvalidArgumentException("Content is not valid, can't split headers and content"); 
        } 
 
        list ($headers, $body) = $splits; 
 
        // Regroup multiline headers 
        $currentHeader = ''; 
        $headerLines = array(); 
        foreach (preg_split('/\r?\n/', $headers) as $line) { 
            if (empty($line)) { 
                continue; 
            } 
            if (preg_match('/^\h+(.+)/', $line, $matches)) { 
                // Multi line header 
                $currentHeader .= ' '.$matches[1]; 
            } else { 
                if (!empty($currentHeader)) { 
                    $headerLines[] = $currentHeader; 
                } 
                $currentHeader = trim($line); 
            } 
        } 
 
        if (!empty($currentHeader)) { 
            $headerLines[] = $currentHeader; 
        } 
 
        // Parse headers 
        $this->headers = array(); 
        foreach ($headerLines as $line) { 
            $lineSplit = explode(':', $line, 2); 
            if (2 === count($lineSplit)) { 
                list($key, $value) = $lineSplit; 
                // Decode value 
                $value = mb_decode_mimeheader(trim($value)); 
            } else { 
                // Bogus header 
                $key = $lineSplit[0]; 
                $value = ''; 
            } 
            // Case-insensitive key 
            $key = strtolower($key); 
            if (!isset($this->headers[$key])) { 
                $this->headers[$key] = $value; 
            } else { 
                if (!is_array($this->headers[$key])) { 
                    $this->headers[$key] = (array)$this->headers[$key]; 
                } 
                $this->headers[$key][] = $value; 
            } 
        } 
 
        // Is MultiPart ? 
        $contentType = $this->getHeader('Content-Type'); 
        if ('multipart' === strstr(self::getHeaderValue($contentType), '/', true)) { 
            // MultiPart ! 
            $this->multipart = true; 
            $boundary = self::getHeaderOption($contentType, 'boundary'); 
 
            if (null === $boundary) { 
                throw new \InvalidArgumentException("Can't find boundary in content type"); 
            } 
 
            $separator = '--'.preg_quote($boundary, '/'); 
 
            // Get multi-part content 
            if (0 === preg_match('/'.$separator.'\r?\n(.+?)\r?\n'.$separator.'--/s', $body, $matches)) { 
                throw new \InvalidArgumentException("Can't find multi-part content"); 
            } 
 
            // Get parts 
            $parts = preg_split('/\r?\n'.$separator.'\r?\n/', $matches[1]); 
 
            foreach ($parts as $part) { 
                $this->parts[] = new self($part); 
            } 
        } else { 
            // Decode 
            $encoding = strtolower($this->getHeader('Content-Transfer-Encoding')); 
            switch ($encoding) { 
                case 'base64': 
                    $body = base64_decode($body); 
                    break; 
                case 'quoted-printable': 
                    $body = quoted_printable_decode($body); 
                    break; 
            } 
 
            // Convert to UTF-8 ( Not if binary or 7bit ( aka Ascii ) ) 
            if (!in_array($encoding, array('binary', '7bit'))) { 
                // Charset 
                $charset = self::getHeaderOption($contentType, 'charset'); 
                if (null === $charset) { 
                    // Try to detect 
                    $charset = mb_detect_encoding($body) ?: 'utf-8'; 
                } 
 
                // Only convert if not UTF-8 
                if ('utf-8' !== strtolower($charset)) { 
                    $body = mb_convert_encoding($body, 'utf-8', $charset); 
                } 
            } 
 
            $this->body = $body; 
        } 
    } 
 
    /** 
     * @return bool 
     */ 
    public function isMultiPart() 
    { 
        return $this->multipart; 
    } 
 
    /** 
     * @return string 
     * @throws \LogicException if is multipart 
     */ 
    public function getBody() 
    { 
        if ($this->isMultiPart()) { 
            throw new \LogicException("MultiPart content, there aren't body"); 
        } else { 
            return $this->body; 
        } 
    } 
 
    /** 
     * @return array 
     */ 
    public function getHeaders() 
    { 
        return $this->headers; 
    } 
 
    /** 
     * @param string $key 
     * @param mixed  $default 
     * @return mixed 
     */ 
    public function getHeader($key, $default = null) 
    { 
        // Case-insensitive key 
        $key = strtolower($key); 
        if (isset($this->headers[$key])) { 
            return $this->headers[$key]; 
        } else { 
            return $default; 
        } 
    } 
 
    /** 
     * @param string $content 
     * @return array 
     */ 
    static protected function parseHeaderContent($content) 
    { 
        $parts = explode(';', $content); 
        $headerValue = array_shift($parts); 
        $options = array(); 
        // Parse options 
        foreach ($parts as $part) { 
            if (!empty($part)) { 
                $partSplit = explode('=', $part, 2); 
                if (2 === count($partSplit)) { 
                    list ($key, $value) = $partSplit; 
                    $options[trim($key)] = trim($value, ' "'); 
                } else { 
                    // Bogus option 
                    $options[$partSplit[0]] = ''; 
                } 
            } 
        } 
 
        return array($headerValue, $options); 
    } 
 
    /** 
     * @param string $header 
     * @return string 
     */ 
    static public function getHeaderValue($header) 
    { 
        list($value) = self::parseHeaderContent($header); 
 
        return $value; 
    } 
 
    /** 
     * @param string $header 
     * @return string 
     */ 
    static public function getHeaderOptions($header) 
    { 
        list(,$options) = self::parseHeaderContent($header); 
 
        return $options; 
    } 
 
    /** 
     * @param string $header 
     * @param string $key 
     * @param mixed  $default 
     * @return mixed 
     */ 
    static public function getHeaderOption($header, $key, $default = null) 
    { 
        $options = self::getHeaderOptions($header); 
 
        if (isset($options[$key])) { 
            return $options[$key]; 
        } else { 
            return $default; 
        } 
    } 
 
    /** 
     * @return string 
     */ 
    public function getMimeType() 
    { 
        // Find Content-Disposition 
        $contentType = $this->getHeader('Content-Type'); 
 
        return self::getHeaderValue($contentType) ?: 'application/octet-stream'; 
    } 
 
    /** 
     * @return string|null 
     */ 
    public function getName() 
    { 
        // Find Content-Disposition 
        $contentDisposition = $this->getHeader('Content-Disposition'); 
 
        return self::getHeaderOption($contentDisposition, 'name'); 
    } 
 
    /** 
     * @return string|null 
     */ 
    public function getFileName() 
    { 
        // Find Content-Disposition 
        $contentDisposition = $this->getHeader('Content-Disposition'); 
 
        return self::getHeaderOption($contentDisposition, 'filename'); 
    } 
 
    /** 
     * @return bool 
     */ 
    public function isFile() 
    { 
        return !is_null($this->getFileName()); 
    } 
 
    /** 
     * @return Part[] 
     * @throws \LogicException if is not multipart 
     */ 
    public function getParts() 
    { 
        if ($this->isMultiPart()) { 
            return $this->parts; 
        } else { 
            throw new \LogicException("Not MultiPart content, there aren't any parts"); 
        } 
    } 
 
    /** 
     * @param string $name 
     * @return Part[] 
     * @throws \LogicException if is not multipart 
     */ 
    public function getPartsByName($name) 
    { 
        $parts = array(); 
 
        foreach ($this->getParts() as $part) { 
            if ($part->getName() === $name) { 
                $parts[] = $part; 
            } 
        } 
 
        return $parts; 
    } 
} 
 
 |