41f8db947877a72b864c30702237c85fe2a9e481
[Packages/TYPO3.CMS.git] / typo3 / sysext / frontend / Classes / Controller / DataSubmissionController.php
1 <?php
2 namespace TYPO3\CMS\Frontend\Controller;
3
4 /***************************************************************
5 * Copyright notice
6 *
7 * (c) 1999-2013 Kasper Skårhøj (kasperYYYY@typo3.com)
8 * All rights reserved
9 *
10 * This script is part of the TYPO3 project. The TYPO3 project is
11 * free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * The GNU General Public License can be found at
17 * http://www.gnu.org/copyleft/gpl.html.
18 * A copy is found in the textfile GPL.txt and important notices to the license
19 * from the author is found in LICENSE.txt distributed with these scripts.
20 *
21 *
22 * This script is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
26 *
27 * This copyright notice MUST APPEAR in all copies of the script!
28 ***************************************************************/
29
30 use TYPO3\CMS\Core\Utility;
31
32 /**
33 * Formmail class
34 *
35 * @author Kasper Skårhøj <kasperYYYY@typo3.com>
36 */
37 class DataSubmissionController {
38
39 protected $reserved_names = 'recipient,recipient_copy,auto_respond_msg,auto_respond_checksum,redirect,subject,attachment,from_email,from_name,replyto_email,replyto_name,organisation,priority,html_enabled,quoted_printable,submit_x,submit_y';
40
41 // Collection of suspicious header data, used for logging
42 protected $dirtyHeaders = array();
43
44 protected $characterSet;
45
46 protected $subject;
47
48 protected $fromName;
49
50 protected $replyToName;
51
52 protected $organisation;
53
54 protected $fromAddress;
55
56 protected $replyToAddress;
57
58 protected $priority;
59
60 protected $autoRespondMessage;
61
62 protected $encoding = 'quoted-printable';
63
64 /**
65 * @var \TYPO3\CMS\Core\Mail\MailMessage
66 */
67 protected $mailMessage;
68
69 protected $recipient;
70
71 protected $plainContent = '';
72
73 /**
74 * @var array Files to clean up at the end (attachments)
75 */
76 protected $temporaryFiles = array();
77
78 /**
79 * Start function
80 * This class is able to generate a mail in formmail-style from the data in $V
81 * Fields:
82 *
83 * [recipient]: email-adress of the one to receive the mail. If array, then all values are expected to be recipients
84 * [attachment]: ....
85 *
86 * [subject]: The subject of the mail
87 * [from_email]: Sender email. If not set, [email] is used
88 * [from_name]: Sender name. If not set, [name] is used
89 * [replyto_email]: Reply-to email. If not set [from_email] is used
90 * [replyto_name]: Reply-to name. If not set [from_name] is used
91 * [organisation]: Organization (header)
92 * [priority]: Priority, 1-5, default 3
93 * [html_enabled]: If mail is sent as html
94 * [use_base64]: If set, base64 encoding will be used instead of quoted-printable
95 *
96 * @param array $valueList Contains values for the field names listed above (with slashes removed if from POST input)
97 * @param boolean $base64 Whether to base64 encode the mail content
98 * @return void
99 * @todo Define visibility
100 */
101 public function start($valueList, $base64 = FALSE) {
102 $this->mailMessage = Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\MailMessage');
103 if ($GLOBALS['TSFE']->config['config']['formMailCharset']) {
104 // Respect formMailCharset if it was set
105 $this->characterSet = $GLOBALS['TSFE']->csConvObj->parse_charset($GLOBALS['TSFE']->config['config']['formMailCharset']);
106 } elseif ($GLOBALS['TSFE']->metaCharset != $GLOBALS['TSFE']->renderCharset) {
107 // Use metaCharset for mail if different from renderCharset
108 $this->characterSet = $GLOBALS['TSFE']->metaCharset;
109 } else {
110 // Otherwise use renderCharset as default
111 $this->characterSet = $GLOBALS['TSFE']->renderCharset;
112 }
113 if ($base64 || $valueList['use_base64']) {
114 $this->encoding = 'base64';
115 }
116 if (isset($valueList['recipient'])) {
117 // Convert form data from renderCharset to mail charset
118 $this->subject = $valueList['subject'] ? $valueList['subject'] : 'Formmail on ' . Utility\GeneralUtility::getIndpEnv('HTTP_HOST');
119 $this->subject = $this->sanitizeHeaderString($this->subject);
120 $this->fromName = $valueList['from_name'] ? $valueList['from_name'] : ($valueList['name'] ? $valueList['name'] : '');
121 $this->fromName = $this->sanitizeHeaderString($this->fromName);
122 $this->replyToName = $valueList['replyto_name'] ? $valueList['replyto_name'] : $this->fromName;
123 $this->replyToName = $this->sanitizeHeaderString($this->replyToName);
124 $this->organisation = $valueList['organisation'] ? $valueList['organisation'] : '';
125 $this->organisation = $this->sanitizeHeaderString($this->organisation);
126 $this->fromAddress = $valueList['from_email'] ? $valueList['from_email'] : ($valueList['email'] ? $valueList['email'] : '');
127 if (!Utility\GeneralUtility::validEmail($this->fromAddress)) {
128 $this->fromAddress = Utility\MailUtility::getSystemFromAddress();
129 $this->fromName = Utility\MailUtility::getSystemFromName();
130 }
131 $this->replyToAddress = $valueList['replyto_email'] ? $valueList['replyto_email'] : $this->fromAddress;
132 $this->priority = $valueList['priority'] ? Utility\MathUtility::forceIntegerInRange($valueList['priority'], 1, 5) : 3;
133 // Auto responder
134 $this->autoRespondMessage = trim($valueList['auto_respond_msg']) && $this->fromAddress ? trim($valueList['auto_respond_msg']) : '';
135 if ($this->autoRespondMessage !== '') {
136 // Check if the value of the auto responder message has been modified with evil intentions
137 $autoRespondChecksum = $valueList['auto_respond_checksum'];
138 $correctHmacChecksum = Utility\GeneralUtility::hmac($this->autoRespondMessage);
139 if ($autoRespondChecksum !== $correctHmacChecksum) {
140 Utility\GeneralUtility::sysLog('Possible misuse of t3lib_formmail auto respond method. Subject: ' . $valueList['subject'], 'Core', Utility\GeneralUtility::SYSLOG_SEVERITY_ERROR);
141 return;
142 } else {
143 $this->autoRespondMessage = $this->sanitizeHeaderString($this->autoRespondMessage);
144 }
145 }
146 $plainTextContent = '';
147 $htmlContent = '<table border="0" cellpadding="2" cellspacing="2">';
148 // Runs through $V and generates the mail
149 if (is_array($valueList)) {
150 foreach ($valueList as $key => $val) {
151 if (!Utility\GeneralUtility::inList($this->reserved_names, $key)) {
152 $space = strlen($val) > 60 ? LF : '';
153 $val = is_array($val) ? implode($val, LF) : $val;
154 // Convert form data from renderCharset to mail charset (HTML may use entities)
155 $plainTextValue = $val;
156 $HtmlValue = htmlspecialchars($val);
157 $plainTextContent .= strtoupper($key) . ': ' . $space . $plainTextValue . LF . $space;
158 $htmlContent .= '<tr><td bgcolor="#eeeeee"><font face="Verdana" size="1"><strong>' . strtoupper($key) . '</strong></font></td><td bgcolor="#eeeeee"><font face="Verdana" size="1">' . nl2br($HtmlValue) . '&nbsp;</font></td></tr>';
159 }
160 }
161 }
162 $htmlContent .= '</table>';
163 $this->plainContent = $plainTextContent;
164 if ($valueList['html_enabled']) {
165 $this->mailMessage->setBody($htmlContent, 'text/html', $this->characterSet);
166 $this->mailMessage->addPart($plainTextContent, 'text/plain', $this->characterSet);
167 } else {
168 $this->mailMessage->setBody($plainTextContent, 'text/plain', $this->characterSet);
169 }
170 for ($a = 0; $a < 10; $a++) {
171 $variableName = 'attachment' . ($a ? $a : '');
172 if (!isset($_FILES[$variableName])) {
173 continue;
174 }
175 if (!is_uploaded_file($_FILES[$variableName]['tmp_name'])) {
176 Utility\GeneralUtility::sysLog('Possible abuse of t3lib_formmail: temporary file "' . $_FILES[$variableName]['tmp_name'] . '" ("' . $_FILES[$variableName]['name'] . '") was not an uploaded file.', 'Core', Utility\GeneralUtility::SYSLOG_SEVERITY_ERROR);
177 }
178 if ($_FILES[$variableName]['tmp_name']['error'] !== UPLOAD_ERR_OK) {
179 Utility\GeneralUtility::sysLog('Error in uploaded file in t3lib_formmail: temporary file "' . $_FILES[$variableName]['tmp_name'] . '" ("' . $_FILES[$variableName]['name'] . '") Error code: ' . $_FILES[$variableName]['tmp_name']['error'], 'Core', Utility\GeneralUtility::SYSLOG_SEVERITY_ERROR);
180 }
181 $theFile = Utility\GeneralUtility::upload_to_tempfile($_FILES[$variableName]['tmp_name']);
182 $theName = $_FILES[$variableName]['name'];
183 if ($theFile && file_exists($theFile)) {
184 if (filesize($theFile) < $GLOBALS['TYPO3_CONF_VARS']['FE']['formmailMaxAttachmentSize']) {
185 $this->mailMessage->attach(\Swift_Attachment::fromPath($theFile)->setFilename($theName));
186 }
187 }
188 $this->temporaryFiles[] = $theFile;
189 }
190 $from = $this->fromName ? array($this->fromAddress => $this->fromName) : array($this->fromAddress);
191 $this->recipient = $this->parseAddresses($valueList['recipient']);
192 $this->mailMessage->setSubject($this->subject)->setFrom($from)->setTo($this->recipient)->setPriority($this->priority);
193 $replyTo = $this->replyToName ? array($this->replyToAddress => $this->replyToName) : array($this->replyToAddress);
194 $this->mailMessage->setReplyTo($replyTo);
195 $this->mailMessage->getHeaders()->addTextHeader('Organization', $this->organisation);
196 if ($valueList['recipient_copy']) {
197 $this->mailMessage->setCc($this->parseAddresses($valueList['recipient_copy']));
198 }
199 $this->mailMessage->setCharset($this->characterSet);
200 // Ignore target encoding. This is handled automatically by Swift Mailer and overriding the defaults
201 // is not worth the trouble
202 // Log dirty header lines
203 if ($this->dirtyHeaders) {
204 Utility\GeneralUtility::sysLog('Possible misuse of t3lib_formmail: see TYPO3 devLog', 'Core', Utility\GeneralUtility::SYSLOG_SEVERITY_ERROR);
205 if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['enable_DLOG']) {
206 Utility\GeneralUtility::devLog('t3lib_formmail: ' . Utility\GeneralUtility::arrayToLogString($this->dirtyHeaders, '', 200), 'Core', 3);
207 }
208 }
209 }
210 }
211
212 /**
213 * Checks string for suspicious characters
214 *
215 * @param string $string String to check
216 * @return string Valid or empty string
217 */
218 protected function sanitizeHeaderString($string) {
219 $pattern = '/[\\r\\n\\f\\e]/';
220 if (preg_match($pattern, $string) > 0) {
221 $this->dirtyHeaders[] = $string;
222 $string = '';
223 }
224 return $string;
225 }
226
227 /**
228 * Parses mailbox headers and turns them into an array.
229 *
230 * Mailbox headers are a comma separated list of 'name <email@example.org' combinations or plain email addresses (or a mix
231 * of these).
232 * The resulting array has key-value pairs where the key is either a number (no display name in the mailbox header) and the
233 * value is the email address, or the key is the email address and the value is the display name.
234 *
235 * @param string $rawAddresses Comma separated list of email addresses (optionally with display name)
236 * @return array Parsed list of addresses.
237 */
238 protected function parseAddresses($rawAddresses = '') {
239 /** @var $addressParser \TYPO3\CMS\Core\Mail\Rfc822AddressesParser */
240 $addressParser = Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\Rfc822AddressesParser', $rawAddresses);
241 $addresses = $addressParser->parseAddressList();
242 $addressList = array();
243 foreach ($addresses as $address) {
244 if ($address->personal) {
245 // Item with name found ( name <email@example.org> )
246 $addressList[$address->mailbox . '@' . $address->host] = $address->personal;
247 } else {
248 // Item without name found ( email@example.org )
249 $addressList[] = $address->mailbox . '@' . $address->host;
250 }
251 }
252 return $addressList;
253 }
254
255 /**
256 * Sends the actual mail and handles autorespond message
257 *
258 * @return boolean
259 */
260 public function sendTheMail() {
261 // Sending the mail requires the recipient and message to be set.
262 if (!$this->mailMessage->getTo() || !trim($this->mailMessage->getBody())) {
263 return FALSE;
264 }
265 $this->mailMessage->send();
266 // Auto response
267 if ($this->autoRespondMessage) {
268 $theParts = explode('/', $this->autoRespondMessage, 2);
269 $theParts[0] = str_replace('###SUBJECT###', $this->subject, $theParts[0]);
270 $theParts[1] = str_replace('/', LF, $theParts[1]);
271 $theParts[1] = str_replace('###MESSAGE###', $this->plainContent, $theParts[1]);
272 /** @var $autoRespondMail \TYPO3\CMS\Core\Mail\MailMessage */
273 $autoRespondMail = Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Mail\\MailMessage');
274 $autoRespondMail->setTo($this->fromAddress)->setSubject($theParts[0])->setFrom($this->recipient)->setBody($theParts[1]);
275 $autoRespondMail->send();
276 }
277 return $this->mailMessage->isSent();
278 }
279
280 /**
281 * Do some cleanup at the end (deleting attachment files)
282 */
283 public function __destruct() {
284 foreach ($this->temporaryFiles as $file) {
285 if (Utility\GeneralUtility::isAllowedAbsPath($file) && Utility\GeneralUtility::isFirstPartOfStr($file, PATH_site . 'typo3temp/upload_temp_')) {
286 Utility\GeneralUtility::unlink_tempfile($file);
287 }
288 }
289 }
290
291 }
292
293 ?>