Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/externals/mimemailparser/MimeMailParser.class.php
12240 views
1
<?php
2
3
require_once('attachment.class.php');
4
5
/**
6
* Fast Mime Mail parser Class using PHP's MailParse Extension
7
* @author [email protected]
8
* @url http://www.fijiwebdesign.com/
9
* @license http://creativecommons.org/licenses/by-sa/3.0/us/
10
* @version $Id$
11
*/
12
class MimeMailParser {
13
14
/**
15
* PHP MimeParser Resource ID
16
*/
17
public $resource;
18
19
/**
20
* A file pointer to email
21
*/
22
public $stream;
23
24
/**
25
* A text of an email
26
*/
27
public $data;
28
29
/**
30
* Stream Resources for Attachments
31
*/
32
public $attachment_streams;
33
34
/**
35
* Inialize some stuff
36
* @return
37
*/
38
public function __construct() {
39
$this->attachment_streams = array();
40
}
41
42
/**
43
* Free the held resouces
44
* @return void
45
*/
46
public function __destruct() {
47
// clear the email file resource
48
if (is_resource($this->stream)) {
49
fclose($this->stream);
50
}
51
// clear the MailParse resource
52
if (is_resource($this->resource)) {
53
mailparse_msg_free($this->resource);
54
}
55
// remove attachment resources
56
foreach($this->attachment_streams as $stream) {
57
fclose($stream);
58
}
59
}
60
61
/**
62
* Set the file path we use to get the email text
63
* @return Object MimeMailParser Instance
64
* @param $mail_path Object
65
*/
66
public function setPath($path) {
67
// should parse message incrementally from file
68
$this->resource = mailparse_msg_parse_file($path);
69
$this->stream = fopen($path, 'r');
70
$this->parse();
71
return $this;
72
}
73
74
/**
75
* Set the Stream resource we use to get the email text
76
* @return Object MimeMailParser Instance
77
* @param $stream Resource
78
*/
79
public function setStream($stream) {
80
81
// streams have to be cached to file first
82
if (get_resource_type($stream) == 'stream') {
83
$tmp_fp = tmpfile();
84
if ($tmp_fp) {
85
while(!feof($stream)) {
86
fwrite($tmp_fp, fread($stream, 2028));
87
}
88
fseek($tmp_fp, 0);
89
$this->stream =& $tmp_fp;
90
} else {
91
throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.');
92
return false;
93
}
94
fclose($stream);
95
} else {
96
$this->stream = $stream;
97
}
98
99
$this->resource = mailparse_msg_create();
100
// parses the message incrementally low memory usage but slower
101
while(!feof($this->stream)) {
102
mailparse_msg_parse($this->resource, fread($this->stream, 2082));
103
}
104
$this->parse();
105
return $this;
106
}
107
108
/**
109
* Set the email text
110
* @return Object MimeMailParser Instance
111
* @param $data String
112
*/
113
public function setText($data) {
114
// NOTE: This has been modified for Phabricator. If the input data does not
115
// end in a newline, Mailparse fails to include the last line in the mail
116
// body. This happens somewhere deep, deep inside the mailparse extension,
117
// so adding a newline here seems like the most straightforward fix.
118
if (!preg_match('/\n\z/', $data)) {
119
$data = $data."\n";
120
}
121
122
$this->resource = mailparse_msg_create();
123
// does not parse incrementally, fast memory hog might explode
124
mailparse_msg_parse($this->resource, $data);
125
$this->data = $data;
126
$this->parse();
127
return $this;
128
}
129
130
/**
131
* Parse the Message into parts
132
* @return void
133
* @private
134
*/
135
private function parse() {
136
$structure = mailparse_msg_get_structure($this->resource);
137
$this->parts = array();
138
foreach($structure as $part_id) {
139
$part = mailparse_msg_get_part($this->resource, $part_id);
140
$this->parts[$part_id] = mailparse_msg_get_part_data($part);
141
}
142
}
143
144
/**
145
* Retrieve the Email Headers
146
* @return Array
147
*/
148
public function getHeaders() {
149
if (isset($this->parts[1])) {
150
return $this->getPartHeaders($this->parts[1]);
151
} else {
152
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');
153
}
154
return false;
155
}
156
/**
157
* Retrieve the raw Email Headers
158
* @return string
159
*/
160
public function getHeadersRaw() {
161
if (isset($this->parts[1])) {
162
return $this->getPartHeaderRaw($this->parts[1]);
163
} else {
164
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');
165
}
166
return false;
167
}
168
169
/**
170
* Retrieve a specific Email Header
171
* @return String
172
* @param $name String Header name
173
*/
174
public function getHeader($name) {
175
if (isset($this->parts[1])) {
176
$headers = $this->getPartHeaders($this->parts[1]);
177
if (isset($headers[$name])) {
178
return $headers[$name];
179
}
180
} else {
181
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email headers.');
182
}
183
return false;
184
}
185
186
/**
187
* Returns the email message body in the specified format
188
* @return Mixed String Body or False if not found
189
* @param $type Object[optional]
190
*/
191
public function getMessageBody($type = 'text') {
192
193
// NOTE: This function has been modified for Phabricator. The default
194
// implementation returns the last matching part, which throws away text
195
// for many emails. Instead, we concatenate all matching parts. See
196
// issue 22 for discussion:
197
// http://code.google.com/p/php-mime-mail-parser/issues/detail?id=22
198
199
$body = false;
200
$mime_types = array(
201
'text'=> 'text/plain',
202
'html'=> 'text/html'
203
);
204
if (in_array($type, array_keys($mime_types))) {
205
foreach($this->parts as $part) {
206
$disposition = $this->getPartContentDisposition($part);
207
if ($disposition == 'attachment') {
208
// text/plain parts with "Content-Disposition: attachment" are
209
// attachments, not part of the text body.
210
continue;
211
}
212
if ($this->getPartContentType($part) == $mime_types[$type]) {
213
$headers = $this->getPartHeaders($part);
214
// Concatenate all the matching parts into the body text. For example,
215
// if a user sends a message with some text, then an image, and then
216
// some more text, the text body of the email gets split over several
217
// attachments.
218
$body .= $this->decode(
219
$this->getPartBody($part),
220
array_key_exists('content-transfer-encoding', $headers)
221
? $headers['content-transfer-encoding']
222
: '');
223
}
224
}
225
} else {
226
throw new Exception('Invalid type specified for MimeMailParser::getMessageBody. "type" can either be text or html.');
227
}
228
return $body;
229
}
230
231
/**
232
* get the headers for the message body part.
233
* @return Array
234
* @param $type Object[optional]
235
*/
236
public function getMessageBodyHeaders($type = 'text') {
237
$headers = false;
238
$mime_types = array(
239
'text'=> 'text/plain',
240
'html'=> 'text/html'
241
);
242
if (in_array($type, array_keys($mime_types))) {
243
foreach($this->parts as $part) {
244
if ($this->getPartContentType($part) == $mime_types[$type]) {
245
$headers = $this->getPartHeaders($part);
246
}
247
}
248
} else {
249
throw new Exception('Invalid type specified for MimeMailParser::getMessageBody. "type" can either be text or html.');
250
}
251
return $headers;
252
}
253
254
/**
255
* Returns the attachments contents in order of appearance
256
* @return Array
257
* @param $type Object[optional]
258
*/
259
public function getAttachments() {
260
// NOTE: This has been modified for Phabricator. Some mail clients do not
261
// send attachments with "Content-Disposition" headers.
262
$attachments = array();
263
$dispositions = array("attachment","inline");
264
$non_attachment_types = array("text/plain", "text/html");
265
$nonameIter = 0;
266
foreach ($this->parts as $part) {
267
$disposition = $this->getPartContentDisposition($part);
268
$filename = 'noname';
269
if (isset($part['disposition-filename'])) {
270
$filename = $part['disposition-filename'];
271
} elseif (isset($part['content-name'])) {
272
// if we have no disposition but we have a content-name, it's a valid attachment.
273
// we simulate the presence of an attachment disposition with a disposition filename
274
$filename = $part['content-name'];
275
$disposition = 'attachment';
276
} elseif (!in_array($part['content-type'], $non_attachment_types, true)
277
&& substr($part['content-type'], 0, 10) !== 'multipart/'
278
) {
279
// if we cannot get it with getMessageBody, we assume it is an attachment
280
$disposition = 'attachment';
281
}
282
283
if (in_array($disposition, $dispositions) && isset($filename) === true) {
284
if ($filename == 'noname') {
285
$nonameIter++;
286
$filename = 'noname'.$nonameIter;
287
}
288
$attachments[] = new MimeMailParser_attachment(
289
$filename,
290
$this->getPartContentType($part),
291
$this->getAttachmentStream($part),
292
$disposition,
293
$this->getPartHeaders($part)
294
);
295
}
296
}
297
return $attachments;
298
}
299
300
/**
301
* Return the Headers for a MIME part
302
* @return Array
303
* @param $part Array
304
*/
305
private function getPartHeaders($part) {
306
if (isset($part['headers'])) {
307
return $part['headers'];
308
}
309
return false;
310
}
311
312
/**
313
* Return a Specific Header for a MIME part
314
* @return Array
315
* @param $part Array
316
* @param $header String Header Name
317
*/
318
private function getPartHeader($part, $header) {
319
if (isset($part['headers'][$header])) {
320
return $part['headers'][$header];
321
}
322
return false;
323
}
324
325
/**
326
* Return the ContentType of the MIME part
327
* @return String
328
* @param $part Array
329
*/
330
private function getPartContentType($part) {
331
if (isset($part['content-type'])) {
332
return $part['content-type'];
333
}
334
return false;
335
}
336
337
/**
338
* Return the Content Disposition
339
* @return String
340
* @param $part Array
341
*/
342
private function getPartContentDisposition($part) {
343
if (isset($part['content-disposition'])) {
344
return $part['content-disposition'];
345
}
346
return false;
347
}
348
349
/**
350
* Retrieve the raw Header of a MIME part
351
* @return String
352
* @param $part Object
353
*/
354
private function getPartHeaderRaw(&$part) {
355
$header = '';
356
if ($this->stream) {
357
$header = $this->getPartHeaderFromFile($part);
358
} else if ($this->data) {
359
$header = $this->getPartHeaderFromText($part);
360
} else {
361
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.');
362
}
363
return $header;
364
}
365
/**
366
* Retrieve the Body of a MIME part
367
* @return String
368
* @param $part Object
369
*/
370
private function getPartBody(&$part) {
371
$body = '';
372
if ($this->stream) {
373
$body = $this->getPartBodyFromFile($part);
374
} else if ($this->data) {
375
$body = $this->getPartBodyFromText($part);
376
} else {
377
throw new Exception('MimeMailParser::setPath() or MimeMailParser::setText() must be called before retrieving email parts.');
378
}
379
return $body;
380
}
381
382
/**
383
* Retrieve the Header from a MIME part from file
384
* @return String Mime Header Part
385
* @param $part Array
386
*/
387
private function getPartHeaderFromFile(&$part) {
388
$start = $part['starting-pos'];
389
$end = $part['starting-pos-body'];
390
fseek($this->stream, $start, SEEK_SET);
391
$header = fread($this->stream, $end-$start);
392
return $header;
393
}
394
/**
395
* Retrieve the Body from a MIME part from file
396
* @return String Mime Body Part
397
* @param $part Array
398
*/
399
private function getPartBodyFromFile(&$part) {
400
$start = $part['starting-pos-body'];
401
$end = $part['ending-pos-body'];
402
fseek($this->stream, $start, SEEK_SET);
403
$body = fread($this->stream, $end-$start);
404
return $body;
405
}
406
407
/**
408
* Retrieve the Header from a MIME part from text
409
* @return String Mime Header Part
410
* @param $part Array
411
*/
412
private function getPartHeaderFromText(&$part) {
413
$start = $part['starting-pos'];
414
$end = $part['starting-pos-body'];
415
$header = substr($this->data, $start, $end-$start);
416
return $header;
417
}
418
/**
419
* Retrieve the Body from a MIME part from text
420
* @return String Mime Body Part
421
* @param $part Array
422
*/
423
private function getPartBodyFromText(&$part) {
424
$start = $part['starting-pos-body'];
425
$end = $part['ending-pos-body'];
426
$body = substr($this->data, $start, $end-$start);
427
return $body;
428
}
429
430
/**
431
* Read the attachment Body and save temporary file resource
432
* @return String Mime Body Part
433
* @param $part Array
434
*/
435
private function getAttachmentStream(&$part) {
436
$temp_fp = tmpfile();
437
438
array_key_exists('content-transfer-encoding', $part['headers']) ? $encoding = $part['headers']['content-transfer-encoding'] : $encoding = '';
439
440
if ($temp_fp) {
441
if ($this->stream) {
442
$start = $part['starting-pos-body'];
443
$end = $part['ending-pos-body'];
444
fseek($this->stream, $start, SEEK_SET);
445
$len = $end-$start;
446
$written = 0;
447
$write = 2028;
448
$body = '';
449
while($written < $len) {
450
if (($written+$write < $len )) {
451
$write = $len - $written;
452
}
453
$part = fread($this->stream, $write);
454
fwrite($temp_fp, $this->decode($part, $encoding));
455
$written += $write;
456
}
457
} else if ($this->data) {
458
$attachment = $this->decode($this->getPartBodyFromText($part), $encoding);
459
fwrite($temp_fp, $attachment, strlen($attachment));
460
}
461
fseek($temp_fp, 0, SEEK_SET);
462
} else {
463
throw new Exception('Could not create temporary files for attachments. Your tmp directory may be unwritable by PHP.');
464
return false;
465
}
466
return $temp_fp;
467
}
468
469
470
/**
471
* Decode the string depending on encoding type.
472
* @return String the decoded string.
473
* @param $encodedString The string in its original encoded state.
474
* @param $encodingType The encoding type from the Content-Transfer-Encoding header of the part.
475
*/
476
private function decode($encodedString, $encodingType) {
477
if (strtolower($encodingType) == 'base64') {
478
return base64_decode($encodedString);
479
} else if (strtolower($encodingType) == 'quoted-printable') {
480
return quoted_printable_decode($encodedString);
481
} else {
482
return $encodedString;
483
}
484
}
485
486
}
487
488
489
?>
490
491