[TASK] Correct DBAL ux_* class mapping in migrations file
[Packages/TYPO3.CMS.git] / t3lib / class.t3lib_readmail.php
1 <?php
2 /***************************************************************
3 * Copyright notice
4 *
5 * (c) 1999-2011 Kasper Skårhøj (kasperYYYY@typo3.com)
6 * All rights reserved
7 *
8 * This script is part of the TYPO3 project. The TYPO3 project is
9 * free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * The GNU General Public License can be found at
15 * http://www.gnu.org/copyleft/gpl.html.
16 * A copy is found in the textfile GPL.txt and important notices to the license
17 * from the author is found in LICENSE.txt distributed with these scripts.
18 *
19 *
20 * This script is distributed in the hope that it will be useful,
21 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 * GNU General Public License for more details.
24 *
25 * This copyright notice MUST APPEAR in all copies of the script!
26 ***************************************************************/
27 /**
28 * Contains a class with functions used to read email content
29 *
30 * Revised for TYPO3 3.6 May 2003 by Kasper Skårhøj
31 *
32 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
33 */
34 /**
35 * Functions used to read email content
36 * The class is still just a bunch of miscellaneous functions used to read content out of emails
37 *
38 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
39 * @package TYPO3
40 * @subpackage t3lib
41 */
42 class t3lib_readmail {
43
44 /**
45 * @todo Define visibility
46 */
47 public $dateAbbrevs = array(
48 'JAN' => 1,
49 'FEB' => 2,
50 'MAR' => 3,
51 'APR' => 4,
52 'MAY' => 5,
53 'JUN' => 6,
54 'JUL' => 7,
55 'AUG' => 8,
56 'SEP' => 9,
57 'OCT' => 10,
58 'NOV' => 11,
59 'DEC' => 12
60 );
61
62 // = +0100 (CET)
63 /**
64 * @todo Define visibility
65 */
66 public $serverGMToffsetMinutes = 60;
67
68 /*******************************
69 *
70 * General
71 *
72 ********************************/
73 /**
74 * Returns the text content of a mail which has previously been parsed by eg. extractMailHeader()
75 * Probably obsolete since the function fullParse() is more advanced and safer to use.
76 *
77 * @param array $mailParts Output from extractMailHeader()
78 * @return string The content.
79 * @todo Define visibility
80 */
81 public function getMessage($mailParts) {
82 if ($mailParts['content-type']) {
83 $CType = $this->getCType($mailParts['content-type']);
84 if ($CType['boundary']) {
85 $parts = $this->getMailBoundaryParts($CType['boundary'], $mailParts['CONTENT']);
86 $c = $this->getTextContent($parts[0]);
87 } else {
88 $c = $this->getTextContent((('Content-Type: ' . $mailParts['content-type']) . '
89 ') . $mailParts['CONTENT']);
90 }
91 } else {
92 $c = $mailParts['CONTENT'];
93 }
94 return $c;
95 }
96
97 /**
98 * Returns the body part of a raw mail message (including headers)
99 * Probably obsolete since the function fullParse() is more advanced and safer to use.
100 *
101 * @param string $content Raw mail content
102 * @return string Body of message
103 * @todo Define visibility
104 */
105 public function getTextContent($content) {
106 $p = $this->extractMailHeader($content);
107 // Here some decoding might be needed...
108 // However we just return what is believed to be the proper notification:
109 return $p['CONTENT'];
110 }
111
112 /**
113 * Splits the body of a mail into parts based on the boundary string given.
114 * Obsolete, use fullParse()
115 *
116 * @param string $boundary Boundary string used to split the content.
117 * @param string $content BODY section of a mail
118 * @return array Parts of the mail based on this
119 * @todo Define visibility
120 */
121 public function getMailBoundaryParts($boundary, $content) {
122 $mParts = explode('--' . $boundary, $content);
123 unset($mParts[0]);
124 $new = array();
125 foreach ($mParts as $val) {
126 if (trim($val) == '--') {
127 break;
128 }
129 $new[] = ltrim($val);
130 }
131 return $new;
132 }
133
134 /**
135 * Returns Content Type plus more.
136 * Obsolete, use fullParse()
137 *
138 * @param string $str "ContentType" string with more
139 * @return array Parts in key/value pairs
140 * @ignore
141 * @todo Define visibility
142 */
143 public function getCType($str) {
144 $parts = explode(';', $str);
145 $cTypes = array();
146 $cTypes['ContentType'] = $parts[0];
147 next($parts);
148 while (list(, $ppstr) = each($parts)) {
149 $mparts = explode('=', $ppstr, 2);
150 if (count($mparts) > 1) {
151 $cTypes[strtolower(trim($mparts[0]))] = preg_replace('/^"/', '', trim(preg_replace('/"$/', '', trim($mparts[1]))));
152 } else {
153 $cTypes[] = $ppstr;
154 }
155 }
156 return $cTypes;
157 }
158
159 /**
160 * Analyses the return-mail content for the Dmailer module - used to find what reason there was for rejecting the mail
161 * Used by the Dmailer, but not exclusively.
162 *
163 * @param string $c Message body/text
164 * @return array Key/value pairs with analysis result. Eg. "reason", "content", "reason_text", "mailserver" etc.
165 * @todo Define visibility
166 */
167 public function analyseReturnError($c) {
168 $cp = array();
169 // QMAIL
170 if (strstr($c, '--- Below this line is a copy of the message.')) {
171 // Splits by the QMAIL divider
172 list($c) = explode('--- Below this line is a copy of the message.', $c);
173 $cp['content'] = trim($c);
174 $parts = explode('>:', $c, 2);
175 $cp['reason_text'] = trim($parts[1]);
176 $cp['mailserver'] = 'Qmail';
177 if (preg_match('/550|no mailbox|account does not exist/i', $cp['reason_text'])) {
178 // 550 Invalid recipient
179 $cp['reason'] = 550;
180 } elseif (stristr($cp['reason_text'], 'couldn\'t find any host named')) {
181 // Bad host
182 $cp['reason'] = 2;
183 } elseif (preg_match('/Error in Header|invalid Message-ID header/i', $cp['reason_text'])) {
184 $cp['reason'] = 554;
185 } else {
186 $cp['reason'] = -1;
187 }
188 } elseif (strstr($c, 'The Postfix program')) {
189 // Postfix
190 $cp['content'] = trim($c);
191 $parts = explode('>:', $c, 2);
192 $cp['reason_text'] = trim($parts[1]);
193 $cp['mailserver'] = 'Postfix';
194 if (stristr($cp['reason_text'], '550')) {
195 // 550 Invalid recipient, User unknown
196 $cp['reason'] = 550;
197 } elseif (stristr($cp['reason_text'], '553')) {
198 // No such user
199 $cp['reason'] = 553;
200 } elseif (stristr($cp['reason_text'], '551')) {
201 // Mailbox full
202 $cp['reason'] = 551;
203 } else {
204 $cp['reason'] = -1;
205 }
206 } else {
207 // No-named:
208 $cp['content'] = trim($c);
209 $cp['reason_text'] = trim(substr($c, 0, 1000));
210 $cp['mailserver'] = 'unknown';
211 if (preg_match('/Unknown Recipient|Delivery failed 550|Receiver not found|User not listed|recipient problem|Delivery to the following recipients failed|User unknown|recipient name is not recognized/i', $cp['reason_text'])) {
212 // 550 Invalid recipient, User unknown
213 $cp['reason'] = 550;
214 } elseif (preg_match('/over quota|mailbox full/i', $cp['reason_text'])) {
215 $cp['reason'] = 551;
216 } elseif (preg_match('/Error in Header/i', $cp['reason_text'])) {
217 $cp['reason'] = 554;
218 } else {
219 $cp['reason'] = -1;
220 }
221 }
222 return $cp;
223 }
224
225 /**
226 * Decodes a header-string with the =?....?= syntax including base64/quoted-printable encoding.
227 *
228 * @param string $str A string (encoded or not) from a mail header, like sender name etc.
229 * @return string The input string, but with the parts in =?....?= decoded.
230 * @todo Define visibility
231 */
232 public function decodeHeaderString($str) {
233 $parts = explode('=?', $str, 2);
234 if (count($parts) == 2) {
235 list($charset, $encType, $encContent) = explode('?', $parts[1], 3);
236 $subparts = explode('?=', $encContent, 2);
237 $encContent = $subparts[0];
238 switch (strtolower($encType)) {
239 case 'q':
240 $encContent = quoted_printable_decode($encContent);
241 $encContent = str_replace('_', ' ', $encContent);
242 break;
243 case 'b':
244 $encContent = base64_decode($encContent);
245 break;
246 }
247 // Calls decodeHeaderString recursively for any subsequent encoded section.
248 $parts[1] = $encContent . $this->decodeHeaderString($subparts[1]);
249 }
250 return implode('', $parts);
251 }
252
253 /**
254 * Extracts name/email parts from a header field (like 'To:' or 'From:' with name/email mixed up.
255 *
256 * @param string $str Value from a header field containing name/email values.
257 * @return array Array with the name and email in. Email is validated, otherwise not set.
258 * @todo Define visibility
259 */
260 public function extractNameEmail($str) {
261 $outArr = array();
262 // Email:
263 $reg = '';
264 preg_match('/<([^>]*)>/', $str, $reg);
265 if (\TYPO3\CMS\Core\Utility\GeneralUtility::validEmail($str)) {
266 $outArr['email'] = $str;
267 } elseif ($reg[1] && \TYPO3\CMS\Core\Utility\GeneralUtility::validEmail($reg[1])) {
268 $outArr['email'] = $reg[1];
269 // Find name:
270 list($namePart) = explode($reg[0], $str);
271 if (trim($namePart)) {
272 $reg = '';
273 preg_match('/"([^"]*)"/', $str, $reg);
274 if (trim($reg[1])) {
275 $outArr['name'] = trim($reg[1]);
276 } else {
277 $outArr['name'] = trim($namePart);
278 }
279 }
280 }
281 return $outArr;
282 }
283
284 /**
285 * Returns the data from the 'content-type' field. That is the boundary, charset and mime-type
286 *
287 * @param string $contentTypeStr "Content-type-string
288 * @return array key/value pairs with the result.
289 * @todo Define visibility
290 */
291 public function getContentTypeData($contentTypeStr) {
292 $outValue = array();
293 $cTypeParts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(';', $contentTypeStr, 1);
294 // Content type, first value is supposed to be the mime-type, whatever after the first is something else.
295 $outValue['_MIME_TYPE'] = $cTypeParts[0];
296 reset($cTypeParts);
297 next($cTypeParts);
298 while (list(, $v) = Each($cTypeParts)) {
299 $reg = '';
300 preg_match('/([^=]*)="(.*)"/i', $v, $reg);
301 if (trim($reg[1]) && trim($reg[2])) {
302 $outValue[strtolower($reg[1])] = $reg[2];
303 }
304 }
305 return $outValue;
306 }
307
308 /**
309 * Makes a UNIX-date based on the timestamp in the 'Date' header field.
310 *
311 * @param string $dateStr String with a timestamp according to email standards.
312 * @return integer The timestamp converted to unix-time in seconds and compensated for GMT/CET ($this->serverGMToffsetMinutes);
313 * @todo Define visibility
314 */
315 public function makeUnixDate($dateStr) {
316 $dateParts = explode(',', $dateStr);
317 $dateStr = count($dateParts) > 1 ? $dateParts[1] : $dateParts[0];
318 $spaceParts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode(' ', $dateStr, 1);
319 $spaceParts[1] = $this->dateAbbrevs[strtoupper($spaceParts[1])];
320 $timeParts = explode(':', $spaceParts[3]);
321 $timeStamp = mktime($timeParts[0], $timeParts[1], $timeParts[2], $spaceParts[1], $spaceParts[0], $spaceParts[2]);
322 $offset = $this->getGMToffset($spaceParts[4]);
323 // Compensates for GMT by subtracting the number of seconds which the date is offset from serverTime
324 $timeStamp -= $offset * 60;
325 return $timeStamp;
326 }
327
328 /**
329 * Parsing the GMT offset value from a mail timestamp.
330 *
331 * @param string $GMT A string like "+0100" or so.
332 * @return integer Minutes to offset the timestamp
333 * @access private
334 * @todo Define visibility
335 */
336 public function getGMToffset($GMT) {
337 $GMToffset = substr($GMT, 1, 2) * 60 + substr($GMT, 3, 2);
338 $GMToffset *= substr($GMT, 0, 1) == '+' ? 1 : -1;
339 $GMToffset -= $this->serverGMToffsetMinutes;
340 return $GMToffset;
341 }
342
343 /**
344 * This returns the mail header items in an array with associative keys and the mail body part in another CONTENT field
345 *
346 * @param string $content Raw mail content
347 * @param integer $limit A safety limit that will put a upper length to how many header chars will be processed. Set to zero means that there is no limit. (Uses a simple substr() to limit the amount of mail data to process to avoid run-away)
348 * @return array An array where each key/value pair is a header-key/value pair. The mail BODY is returned in the key 'CONTENT' if $limit is not set!
349 * @todo Define visibility
350 */
351 public function extractMailHeader($content, $limit = 0) {
352 if ($limit) {
353 $content = substr($content, 0, $limit);
354 }
355 $lines = explode(LF, ltrim($content));
356 $headers = array();
357 $p = '';
358 foreach ($lines as $k => $str) {
359 if (!trim($str)) {
360 break;
361 }
362 // Header finished
363 $parts = explode(' ', $str, 2);
364 if ($parts[0] && substr($parts[0], -1) == ':') {
365 $p = strtolower(substr($parts[0], 0, -1));
366 if (isset($headers[$p])) {
367 $headers[$p . '.'][] = $headers[$p];
368 $headers[$p] = '';
369 }
370 $headers[$p] = trim($parts[1]);
371 } else {
372 $headers[$p] .= ' ' . trim($str);
373 }
374 unset($lines[$k]);
375 }
376 if (!$limit) {
377 $headers['CONTENT'] = ltrim(implode(LF, $lines));
378 }
379 return $headers;
380 }
381
382 /**
383 * The extended version of the extractMailHeader() which will also parse all the content body into an array and further process the header fields and decode content etc. Returns every part of the mail ready to go.
384 *
385 * @param string $content Raw email input.
386 * @return array Multidimensional array with all parts of the message organized nicely. Use t3lib_utility_Debug::debug() to analyse it visually.
387 * @todo Define visibility
388 */
389 public function fullParse($content) {
390 // *************************
391 // PROCESSING the HEADER part of the mail
392 // *************************
393 // Splitting header and body of mail:
394 $mailParts = $this->extractMailHeader($content);
395 // Decoding header values which potentially can be encoded by =?...?=
396 $list = explode(',', 'subject,thread-topic,from,to');
397 foreach ($list as $headerType) {
398 if (isset($mailParts[$headerType])) {
399 $mailParts[$headerType] = $this->decodeHeaderString($mailParts[$headerType]);
400 }
401 }
402 // Separating email/names from header fields which can contain email addresses.
403 $list = explode(',', 'from,to,reply-to,sender,return-path');
404 foreach ($list as $headerType) {
405 if (isset($mailParts[$headerType])) {
406 $mailParts['_' . strtoupper($headerType)] = $this->extractNameEmail($mailParts[$headerType]);
407 }
408 }
409 // Decode date from human-readable format to unix-time (includes compensation for GMT CET)
410 $mailParts['_DATE'] = $this->makeUnixDate($mailParts['date']);
411 // Transfer encodings of body content
412 switch (strtolower($mailParts['content-transfer-encoding'])) {
413 case 'quoted-printable':
414 $mailParts['CONTENT'] = quoted_printable_decode($mailParts['CONTENT']);
415 break;
416 case 'base64':
417 $mailParts['CONTENT'] = base64_decode($mailParts['CONTENT']);
418 break;
419 }
420 // Content types
421 $mailParts['_CONTENT_TYPE_DAT'] = $this->getContentTypeData($mailParts['content-type']);
422 // *************************
423 // PROCESSING the CONTENT part of the mail (the body)
424 // *************************
425 $cType = strtolower($mailParts['_CONTENT_TYPE_DAT']['_MIME_TYPE']);
426 // Only looking for 'multipart' in string.
427 $cType = substr($cType, 0, 9);
428 switch ($cType) {
429 case 'multipart':
430 if ($mailParts['_CONTENT_TYPE_DAT']['boundary']) {
431 $contentSectionParts = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode('--' . $mailParts['_CONTENT_TYPE_DAT']['boundary'], $mailParts['CONTENT'], 1);
432 $contentSectionParts_proc = array();
433 foreach ($contentSectionParts as $k => $v) {
434 if (substr($v, 0, 2) == '--') {
435 break;
436 }
437 $contentSectionParts_proc[$k] = $this->fullParse($v);
438 }
439 $mailParts['CONTENT'] = $contentSectionParts_proc;
440 } else {
441 $mailParts['CONTENT'] = 'ERROR: No boundary found.';
442 }
443 break;
444 default:
445 if (strtolower($mailParts['_CONTENT_TYPE_DAT']['charset']) == 'utf-8') {
446 $mailParts['CONTENT'] = utf8_decode($mailParts['CONTENT']);
447 }
448 break;
449 }
450 return $mailParts;
451 }
452
453 }
454
455 ?>