File indexing completed on 2024-12-29 05:27:23

0001 <?php
0002 namespace GuzzleHttp\Psr7;
0003 
0004 use Psr\Http\Message\StreamInterface;
0005 
0006 /**
0007  * Stream that when read returns bytes for a streaming multipart or
0008  * multipart/form-data stream.
0009  */
0010 class MultipartStream implements StreamInterface
0011 {
0012     use StreamDecoratorTrait;
0013 
0014     private $boundary;
0015 
0016     /**
0017      * @param array  $elements Array of associative arrays, each containing a
0018      *                         required "name" key mapping to the form field,
0019      *                         name, a required "contents" key mapping to a
0020      *                         StreamInterface/resource/string, an optional
0021      *                         "headers" associative array of custom headers,
0022      *                         and an optional "filename" key mapping to a
0023      *                         string to send as the filename in the part.
0024      * @param string $boundary You can optionally provide a specific boundary
0025      *
0026      * @throws \InvalidArgumentException
0027      */
0028     public function __construct(array $elements = [], $boundary = null)
0029     {
0030         $this->boundary = $boundary ?: sha1(uniqid('', true));
0031         $this->stream = $this->createStream($elements);
0032     }
0033 
0034     /**
0035      * Get the boundary
0036      *
0037      * @return string
0038      */
0039     public function getBoundary()
0040     {
0041         return $this->boundary;
0042     }
0043 
0044     public function isWritable()
0045     {
0046         return false;
0047     }
0048 
0049     /**
0050      * Get the headers needed before transferring the content of a POST file
0051      */
0052     private function getHeaders(array $headers)
0053     {
0054         $str = '';
0055         foreach ($headers as $key => $value) {
0056             $str .= "{$key}: {$value}\r\n";
0057         }
0058 
0059         return "--{$this->boundary}\r\n" . trim($str) . "\r\n\r\n";
0060     }
0061 
0062     /**
0063      * Create the aggregate stream that will be used to upload the POST data
0064      */
0065     protected function createStream(array $elements)
0066     {
0067         $stream = new AppendStream();
0068 
0069         foreach ($elements as $element) {
0070             $this->addElement($stream, $element);
0071         }
0072 
0073         // Add the trailing boundary with CRLF
0074         $stream->addStream(stream_for("--{$this->boundary}--\r\n"));
0075 
0076         return $stream;
0077     }
0078 
0079     private function addElement(AppendStream $stream, array $element)
0080     {
0081         foreach (['contents', 'name'] as $key) {
0082             if (!array_key_exists($key, $element)) {
0083                 throw new \InvalidArgumentException("A '{$key}' key is required");
0084             }
0085         }
0086 
0087         $element['contents'] = stream_for($element['contents']);
0088 
0089         if (empty($element['filename'])) {
0090             $uri = $element['contents']->getMetadata('uri');
0091             if (substr($uri, 0, 6) !== 'php://') {
0092                 $element['filename'] = $uri;
0093             }
0094         }
0095 
0096         list($body, $headers) = $this->createElement(
0097             $element['name'],
0098             $element['contents'],
0099             isset($element['filename']) ? $element['filename'] : null,
0100             isset($element['headers']) ? $element['headers'] : []
0101         );
0102 
0103         $stream->addStream(stream_for($this->getHeaders($headers)));
0104         $stream->addStream($body);
0105         $stream->addStream(stream_for("\r\n"));
0106     }
0107 
0108     /**
0109      * @return array
0110      */
0111     private function createElement($name, StreamInterface $stream, $filename, array $headers)
0112     {
0113         // Set a default content-disposition header if one was no provided
0114         $disposition = $this->getHeader($headers, 'content-disposition');
0115         if (!$disposition) {
0116             $headers['Content-Disposition'] = ($filename === '0' || $filename)
0117                 ? sprintf('form-data; name="%s"; filename="%s"',
0118                     $name,
0119                     basename($filename))
0120                 : "form-data; name=\"{$name}\"";
0121         }
0122 
0123         // Set a default content-length header if one was no provided
0124         $length = $this->getHeader($headers, 'content-length');
0125         if (!$length) {
0126             if ($length = $stream->getSize()) {
0127                 $headers['Content-Length'] = (string) $length;
0128             }
0129         }
0130 
0131         // Set a default Content-Type if one was not supplied
0132         $type = $this->getHeader($headers, 'content-type');
0133         if (!$type && ($filename === '0' || $filename)) {
0134             if ($type = mimetype_from_filename($filename)) {
0135                 $headers['Content-Type'] = $type;
0136             }
0137         }
0138 
0139         return [$stream, $headers];
0140     }
0141 
0142     private function getHeader(array $headers, $key)
0143     {
0144         $lowercaseHeader = strtolower($key);
0145         foreach ($headers as $k => $v) {
0146             if (strtolower($k) === $lowercaseHeader) {
0147                 return $v;
0148             }
0149         }
0150 
0151         return null;
0152     }
0153 }