1<?php 2 3/** 4 * @see https://github.com/laminas/laminas-mail for the canonical source repository 5 * @copyright https://github.com/laminas/laminas-mail/blob/master/COPYRIGHT.md 6 * @license https://github.com/laminas/laminas-mail/blob/master/LICENSE.md New BSD License 7 */ 8 9namespace Laminas\Mail\Storage\Part; 10 11use Laminas\Mail\Headers; 12use Laminas\Mail\Storage\Part; 13 14class File extends Part 15{ 16 protected $contentPos = []; 17 protected $partPos = []; 18 protected $fh; 19 20 /** 21 * Public constructor 22 * 23 * This handler supports the following params: 24 * - file filename or open file handler with message content (required) 25 * - startPos start position of message or part in file (default: current position) 26 * - endPos end position of message or part in file (default: end of file) 27 * - EOL end of Line for messages 28 * 29 * @param array $params full message with or without headers 30 * @throws Exception\RuntimeException 31 * @throws Exception\InvalidArgumentException 32 */ 33 public function __construct(array $params) 34 { 35 if (empty($params['file'])) { 36 throw new Exception\InvalidArgumentException('no file given in params'); 37 } 38 39 if (! is_resource($params['file'])) { 40 $this->fh = fopen($params['file'], 'r'); 41 } else { 42 $this->fh = $params['file']; 43 } 44 if (! $this->fh) { 45 throw new Exception\RuntimeException('could not open file'); 46 } 47 if (isset($params['startPos'])) { 48 fseek($this->fh, $params['startPos']); 49 } 50 $header = ''; 51 $endPos = isset($params['endPos']) ? $params['endPos'] : null; 52 while (($endPos === null || ftell($this->fh) < $endPos) && trim($line = fgets($this->fh))) { 53 $header .= $line; 54 } 55 56 if (isset($params['EOL'])) { 57 $this->headers = Headers::fromString($header, $params['EOL']); 58 } else { 59 $this->headers = Headers::fromString($header); 60 } 61 62 $this->contentPos[0] = ftell($this->fh); 63 if ($endPos !== null) { 64 $this->contentPos[1] = $endPos; 65 } else { 66 fseek($this->fh, 0, SEEK_END); 67 $this->contentPos[1] = ftell($this->fh); 68 } 69 if (! $this->isMultipart()) { 70 return; 71 } 72 73 $boundary = $this->getHeaderField('content-type', 'boundary'); 74 if (! $boundary) { 75 throw new Exception\RuntimeException('no boundary found in content type to split message'); 76 } 77 78 $part = []; 79 $pos = $this->contentPos[0]; 80 fseek($this->fh, $pos); 81 while (! feof($this->fh) && ($endPos === null || $pos < $endPos)) { 82 $line = fgets($this->fh); 83 if ($line === false) { 84 if (feof($this->fh)) { 85 break; 86 } 87 throw new Exception\RuntimeException('error reading file'); 88 } 89 90 $lastPos = $pos; 91 $pos = ftell($this->fh); 92 $line = trim($line); 93 94 if ($line == '--' . $boundary) { 95 if ($part) { 96 // not first part 97 $part[1] = $lastPos; 98 $this->partPos[] = $part; 99 } 100 $part = [$pos]; 101 } elseif ($line == '--' . $boundary . '--') { 102 $part[1] = $lastPos; 103 $this->partPos[] = $part; 104 break; 105 } 106 } 107 $this->countParts = count($this->partPos); 108 } 109 110 /** 111 * Body of part 112 * 113 * If part is multipart the raw content of this part with all sub parts is returned 114 * 115 * @param resource $stream Optional 116 * @return string body 117 */ 118 public function getContent($stream = null) 119 { 120 fseek($this->fh, $this->contentPos[0]); 121 if ($stream !== null) { 122 return stream_copy_to_stream($this->fh, $stream, $this->contentPos[1] - $this->contentPos[0]); 123 } 124 $length = $this->contentPos[1] - $this->contentPos[0]; 125 return $length < 1 ? '' : fread($this->fh, $length); 126 } 127 128 /** 129 * Return size of part 130 * 131 * Quite simple implemented currently (not decoding). Handle with care. 132 * 133 * @return int size 134 */ 135 public function getSize() 136 { 137 return $this->contentPos[1] - $this->contentPos[0]; 138 } 139 140 /** 141 * Get part of multipart message 142 * 143 * @param int $num number of part starting with 1 for first part 144 * @throws Exception\RuntimeException 145 * @return Part wanted part 146 */ 147 public function getPart($num) 148 { 149 --$num; 150 if (! isset($this->partPos[$num])) { 151 throw new Exception\RuntimeException('part not found'); 152 } 153 154 return new static(['file' => $this->fh, 'startPos' => $this->partPos[$num][0], 155 'endPos' => $this->partPos[$num][1]]); 156 } 157} 158