@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
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}