@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at recaptime-dev/main 276 lines 6.9 kB view raw
1<?php 2 3namespace PhpMimeMailParser; 4 5use function var_dump; 6 7/** 8 * Attachment of php-mime-mail-parser 9 * 10 * Fully Tested Mailparse Extension Wrapper for PHP 5.4+ 11 * 12 */ 13class Attachment 14{ 15 /** 16 * @var string $filename Filename 17 */ 18 protected $filename; 19 20 /** 21 * @var string $contentType Mime Type 22 */ 23 protected $contentType; 24 25 /** 26 * @var string $content File Content 27 */ 28 protected $content; 29 30 /** 31 * @var string $contentDisposition Content-Disposition (attachment or inline) 32 */ 33 protected $contentDisposition; 34 35 /** 36 * @var string $contentId Content-ID 37 */ 38 protected $contentId; 39 40 /** 41 * @var array $headers An Array of the attachment headers 42 */ 43 protected $headers; 44 45 /** 46 * @var resource $stream 47 */ 48 protected $stream; 49 50 /** 51 * @var string $mimePartStr 52 */ 53 protected $mimePartStr; 54 55 /** 56 * @var integer $maxDuplicateNumber 57 */ 58 public $maxDuplicateNumber = 100; 59 60 /** 61 * Attachment constructor. 62 * 63 * @param string $filename 64 * @param string $contentType 65 * @param resource $stream 66 * @param string $contentDisposition 67 * @param string $contentId 68 * @param array $headers 69 * @param string $mimePartStr 70 */ 71 public function __construct( 72 $filename, 73 $contentType, 74 $stream, 75 $contentDisposition = 'attachment', 76 $contentId = '', 77 $headers = [], 78 $mimePartStr = '' 79 ) { 80 $this->filename = $filename; 81 $this->contentType = $contentType; 82 $this->stream = $stream; 83 $this->content = null; 84 $this->contentDisposition = $contentDisposition; 85 $this->contentId = $contentId; 86 $this->headers = $headers; 87 $this->mimePartStr = $mimePartStr; 88 } 89 90 /** 91 * retrieve the attachment filename 92 * 93 * @return string 94 */ 95 public function getFilename() 96 { 97 return $this->filename; 98 } 99 100 /** 101 * Retrieve the Attachment Content-Type 102 * 103 * @return string 104 */ 105 public function getContentType() 106 { 107 return $this->contentType; 108 } 109 110 /** 111 * Retrieve the Attachment Content-Disposition 112 * 113 * @return string 114 */ 115 public function getContentDisposition() 116 { 117 return $this->contentDisposition; 118 } 119 120 /** 121 * Retrieve the Attachment Content-ID 122 * 123 * @return string 124 */ 125 public function getContentID() 126 { 127 return $this->contentId; 128 } 129 130 /** 131 * Retrieve the Attachment Headers 132 * 133 * @return array 134 */ 135 public function getHeaders() 136 { 137 return $this->headers; 138 } 139 140 /** 141 * Get a handle to the stream 142 * 143 * @return resource 144 */ 145 public function getStream() 146 { 147 return $this->stream; 148 } 149 150 /** 151 * Rename a file if it already exists at its destination. 152 * Renaming is done by adding a duplicate number to the file name. E.g. existingFileName_1.ext. 153 * After a max duplicate number, renaming the file will switch over to generating a random suffix. 154 * 155 * @param string $fileName Complete path to the file. 156 * @return string The suffixed file name. 157 */ 158 protected function suffixFileName(string $fileName): string 159 { 160 $pathInfo = pathinfo($fileName); 161 $dirname = $pathInfo['dirname'].DIRECTORY_SEPARATOR; 162 $filename = $pathInfo['filename']; 163 $extension = empty($pathInfo['extension']) ? '' : '.'.$pathInfo['extension']; 164 165 $i = 0; 166 do { 167 $i++; 168 169 if ($i > $this->maxDuplicateNumber) { 170 $duplicateExtension = uniqid(); 171 } else { 172 $duplicateExtension = $i; 173 } 174 175 $resultName = $dirname.$filename."_$duplicateExtension".$extension; 176 } while (file_exists($resultName)); 177 178 return $resultName; 179 } 180 181 /** 182 * Read the contents a few bytes at a time until completed 183 * Once read to completion, it always returns false 184 * 185 * @param int $bytes (default: 2082) 186 * 187 * @return string|bool 188 */ 189 public function read($bytes = 2082) 190 { 191 return feof($this->stream) ? false : fread($this->stream, $bytes); 192 } 193 194 /** 195 * Retrieve the file content in one go 196 * Once you retrieve the content you cannot use MimeMailParser_attachment::read() 197 * 198 * @return string 199 */ 200 public function getContent() 201 { 202 if ($this->content === null) { 203 fseek($this->stream, 0); 204 while (($buf = $this->read()) !== false) { 205 $this->content .= $buf; 206 } 207 } 208 209 return $this->content; 210 } 211 212 /** 213 * Get mime part string for this attachment 214 * 215 * @return string 216 */ 217 public function getMimePartStr() 218 { 219 return $this->mimePartStr; 220 } 221 222 /** 223 * Save the attachment individually 224 * 225 * @param string $attach_dir 226 * @param string $filenameStrategy 227 * 228 * @return string 229 */ 230 public function save( 231 $attach_dir, 232 $filenameStrategy = Parser::ATTACHMENT_DUPLICATE_SUFFIX 233 ) { 234 $attach_dir = rtrim($attach_dir, DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR; 235 if (!is_dir($attach_dir)) { 236 mkdir($attach_dir); 237 } 238 239 // Determine filename 240 switch ($filenameStrategy) { 241 case Parser::ATTACHMENT_RANDOM_FILENAME: 242 $fileInfo = pathinfo($this->getFilename()); 243 $extension = empty($fileInfo['extension']) ? '' : '.'.$fileInfo['extension']; 244 $attachment_path = $attach_dir.bin2hex(random_bytes(16)).$extension; 245 break; 246 case Parser::ATTACHMENT_DUPLICATE_THROW: 247 case Parser::ATTACHMENT_DUPLICATE_SUFFIX: 248 $attachment_path = $attach_dir.$this->getFilename(); 249 break; 250 default: 251 throw new Exception('Invalid filename strategy argument provided.'); 252 } 253 254 // Handle duplicate filename 255 if (file_exists($attachment_path)) { 256 switch ($filenameStrategy) { 257 case Parser::ATTACHMENT_DUPLICATE_THROW: 258 throw new Exception('Could not create file for attachment: duplicate filename.'); 259 case Parser::ATTACHMENT_DUPLICATE_SUFFIX: 260 $attachment_path = $this->suffixFileName($attachment_path); 261 break; 262 } 263 } 264 265 /** @var resource $fp */ 266 if ($fp = fopen($attachment_path, 'w')) { 267 while ($bytes = $this->read()) { 268 fwrite($fp, $bytes); 269 } 270 fclose($fp); 271 return realpath($attachment_path); 272 } else { 273 throw new Exception('Could not write attachments. Your directory may be unwritable by PHP.'); 274 } 275 } 276}