<?php 
namespace GuzzleHttp\Psr7; 
 
use Psr\Http\Message\StreamInterface; 
 
/** 
 * Stream that when read returns bytes for a streaming multipart or 
 * multipart/form-data stream. 
 */ 
class MultipartStream implements StreamInterface 
{ 
    use StreamDecoratorTrait; 
 
    private $boundary; 
 
    /** 
     * @param array  $elements Array of associative arrays, each containing a 
     *                         required "name" key mapping to the form field, 
     *                         name, a required "contents" key mapping to a 
     *                         StreamInterface/resource/string, an optional 
     *                         "headers" associative array of custom headers, 
     *                         and an optional "filename" key mapping to a 
     *                         string to send as the filename in the part. 
     * @param string $boundary You can optionally provide a specific boundary 
     * 
     * @throws \InvalidArgumentException 
     */ 
    public function __construct(array $elements = [], $boundary = null) 
    { 
        $this->boundary = $boundary ?: uniqid(); 
        $this->stream = $this->createStream($elements); 
    } 
 
    /** 
     * Get the boundary 
     * 
     * @return string 
     */ 
    public function getBoundary() 
    { 
        return $this->boundary; 
    } 
 
    public function isWritable() 
    { 
        return false; 
    } 
 
    /** 
     * Get the headers needed before transferring the content of a POST file 
     */ 
    private function getHeaders(array $headers) 
    { 
        $str = ''; 
        foreach ($headers as $key => $value) { 
            $str .= "{$key}: {$value}\r\n"; 
        } 
 
        return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n"; 
    } 
 
    /** 
     * Create the aggregate stream that will be used to upload the POST data 
     */ 
    protected function createStream(array $elements) 
    { 
        $stream = new AppendStream(); 
 
        foreach ($elements as $element) { 
            $this->addElement($stream, $element); 
        } 
 
        // Add the trailing boundary with CRLF 
        $stream->addStream(stream_for("--{$this->boundary}--\r\n")); 
 
        return $stream; 
    } 
 
    private function addElement(AppendStream $stream, array $element) 
    { 
        foreach (['contents', 'name'] as $key) { 
            if (!array_key_exists($key, $element)) { 
                throw new \InvalidArgumentException("A '{$key}' key is required"); 
            } 
        } 
 
        $element['contents'] = stream_for($element['contents']); 
 
        if (empty($element['filename'])) { 
            $uri = $element['contents']->getMetadata('uri'); 
            if (substr($uri, 0, 6) !== 'php://') { 
                $element['filename'] = $uri; 
            } 
        } 
 
        list($body, $headers) = $this->createElement( 
            $element['name'], 
            $element['contents'], 
            isset($element['filename']) ? $element['filename'] : null, 
            isset($element['headers']) ? $element['headers'] : [] 
        ); 
 
        $stream->addStream(stream_for($this->getHeaders($headers))); 
        $stream->addStream($body); 
        $stream->addStream(stream_for("\r\n")); 
    } 
 
    /** 
     * @return array 
     */ 
    private function createElement($name, $stream, $filename, array $headers) 
    { 
        // Set a default content-disposition header if one was no provided 
        $disposition = $this->getHeader($headers, 'content-disposition'); 
        if (!$disposition) { 
            $headers['Content-Disposition'] = ($filename === '0' || $filename) 
                ? sprintf('form-data; name="%s"; filename="%s"', 
                    $name, 
                    basename($filename)) 
                : "form-data; name=\"{$name}\""; 
        } 
 
        // Set a default content-length header if one was no provided 
        $length = $this->getHeader($headers, 'content-length'); 
        if (!$length) { 
            if ($length = $stream->getSize()) { 
                $headers['Content-Length'] = (string) $length; 
            } 
        } 
 
        // Set a default Content-Type if one was not supplied 
        $type = $this->getHeader($headers, 'content-type'); 
        if (!$type && ($filename === '0' || $filename)) { 
            if ($type = mimetype_from_filename($filename)) { 
                $headers['Content-Type'] = $type; 
            } 
        } 
 
        return [$stream, $headers]; 
    } 
 
    private function getHeader(array $headers, $key) 
    { 
        $lowercaseHeader = strtolower($key); 
        foreach ($headers as $k => $v) { 
            if (strtolower($k) === $lowercaseHeader) { 
                return $v; 
            } 
        } 
 
        return null; 
    } 
} 
 
 |