Failed Conditions
Push — pr-3115 ( f9aa34 )
by Andreas
02:44
created

inc/mail.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Mail functions
4
 *
5
 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
6
 * @author     Andreas Gohr <[email protected]>
7
 */
8
9
// end of line for mail lines - RFC822 says CRLF but postfix (and other MTAs?)
10
// think different
11
use dokuwiki\Extension\Event;
12
13
if(!defined('MAILHEADER_EOL')) define('MAILHEADER_EOL',"\n");
14
#define('MAILHEADER_ASCIIONLY',1);
15
16
/**
17
 * Patterns for use in email detection and validation
18
 *
19
 * NOTE: there is an unquoted '/' in RFC2822_ATEXT, it must remain unquoted to be used in the parser
20
 * the pattern uses non-capturing groups as captured groups aren't allowed in the parser
21
 * select pattern delimiters with care!
22
 *
23
 * May not be completly RFC conform!
24
 * @link http://www.faqs.org/rfcs/rfc2822.html (paras 3.4.1 & 3.2.4)
25
 *
26
 * @author Chris Smith <[email protected]>
27
 * Check if a given mail address is valid
28
 */
29
if (!defined('RFC2822_ATEXT')) define('RFC2822_ATEXT',"0-9a-zA-Z!#$%&'*+/=?^_`{|}~-");
30
if (!defined('PREG_PATTERN_VALID_EMAIL')) define(
31
    'PREG_PATTERN_VALID_EMAIL',
32
    '['.RFC2822_ATEXT.']+(?:\.['.RFC2822_ATEXT.']+)*@(?i:[0-9a-z][0-9a-z-]*\.)+(?i:[a-z]{2,63})'
33
);
34
35
/**
36
 * Prepare mailfrom replacement patterns
37
 *
38
 * Also prepares a mailfromnobody config that contains an autoconstructed address
39
 * if the mailfrom one is userdependent and this might not be wanted (subscriptions)
40
 *
41
 * @author Andreas Gohr <[email protected]>
42
 */
43
function mail_setup(){
44
    global $conf;
45
    global $USERINFO;
46
    /** @var Input $INPUT */
47
    global $INPUT;
48
49
    // auto constructed address
50
    $host = @parse_url(DOKU_URL,PHP_URL_HOST);
51
    if(!$host) $host = 'example.com';
52
    $noreply = 'noreply@'.$host;
53
54
    $replace = array();
55
    if(!empty($USERINFO['mail'])){
56
        $replace['@MAIL@'] = $USERINFO['mail'];
57
    }else{
58
        $replace['@MAIL@'] = $noreply;
59
    }
60
61
    // use 'noreply' if no user
62
    $replace['@USER@'] = $INPUT->server->str('REMOTE_USER', 'noreply', true);
63
64
    if(!empty($USERINFO['name'])){
65
        $replace['@NAME@'] = $USERINFO['name'];
66
    }else{
67
        $replace['@NAME@'] = '';
68
    }
69
70
    // apply replacements
71
    $from = str_replace(array_keys($replace),
72
                        array_values($replace),
73
                        $conf['mailfrom']);
74
75
    // any replacements done? set different mailfromnone
76
    if($from != $conf['mailfrom']){
77
        $conf['mailfromnobody'] = $noreply;
78
    }else{
79
        $conf['mailfromnobody'] = $from;
80
    }
81
    $conf['mailfrom'] = $from;
82
}
83
84
/**
85
 * UTF-8 autoencoding replacement for PHPs mail function
86
 *
87
 * Email address fields (To, From, Cc, Bcc can contain a textpart and an address
88
 * like this: 'Andreas Gohr <[email protected]>' - the text part is encoded
89
 * automatically. You can seperate receivers by commas.
90
 *
91
 * @param string $to      Receiver of the mail (multiple seperated by commas)
92
 * @param string $subject Mailsubject
93
 * @param string $body    Messagebody
94
 * @param string $from    Sender address
95
 * @param string $cc      CarbonCopy receiver (multiple seperated by commas)
96
 * @param string $bcc     BlindCarbonCopy receiver (multiple seperated by commas)
97
 * @param string $headers Additional Headers (seperated by MAILHEADER_EOL
98
 * @param string $params  Additonal Sendmail params (passed to mail())
99
 *
100
 * @author Andreas Gohr <[email protected]>
101
 * @see    mail()
102
 *
103
 * @deprecated User the Mailer:: class instead
104
 */
105
function mail_send($to, $subject, $body, $from='', $cc='', $bcc='', $headers=null, $params=null){
106
    dbg_deprecated('class Mailer::');
107
    $message = compact('to','subject','body','from','cc','bcc','headers','params');
108
    return Event::createAndTrigger('MAIL_MESSAGE_SEND',$message,'_mail_send_action');
109
}
110
111
/**
112
 * @param $data
113
 * @return bool
114
 *
115
 * @deprecated User the Mailer:: class instead
116
 */
117
function _mail_send_action($data) {
118
    dbg_deprecated('class Mailer::');
119
    // retrieve parameters from event data, $to, $subject, $body, $from, $cc, $bcc, $headers, $params
120
    $to = $data['to'];
121
    $subject = $data['subject'];
122
    $body = $data['body'];
123
124
    // add robustness in case plugin removes any of these optional values
125
    $from = isset($data['from']) ? $data['from'] : '';
126
    $cc = isset($data['cc']) ? $data['cc'] : '';
127
    $bcc = isset($data['bcc']) ? $data['bcc'] : '';
128
    $headers = isset($data['headers']) ? $data['headers'] : null;
129
    $params = isset($data['params']) ? $data['params'] : null;
130
131
    // discard mail request if no recipients are available
132
    if(trim($to) === '' && trim($cc) === '' && trim($bcc) === '') return false;
133
134
    // end additional code to support event ... original mail_send() code from here
135
136
    if(defined('MAILHEADER_ASCIIONLY')){
137
        $subject = \dokuwiki\Utf8\Clean::deaccent($subject);
138
        $subject = \dokuwiki\Utf8\Clean::strip($subject);
139
    }
140
141
    if(!\dokuwiki\Utf8\Clean::isASCII($subject)) {
142
        $enc_subj = '=?UTF-8?Q?'.mail_quotedprintable_encode($subject,0).'?=';
143
        // Spaces must be encoded according to rfc2047. Use the "_" shorthand
144
        $enc_subj = preg_replace('/ /', '_', $enc_subj);
145
146
        // quoted printable has length restriction, use base64 if needed
147
        if(strlen($subject) > 74){
148
            $enc_subj = '=?UTF-8?B?'.base64_encode($subject).'?=';
149
        }
150
151
        $subject = $enc_subj;
152
    }
153
154
    $header  = '';
155
156
    // No named recipients for To: in Windows (see FS#652)
157
    $usenames = (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') ? false : true;
158
159
    $to = mail_encode_address($to,'',$usenames);
0 ignored issues
show
Deprecated Code introduced by
The function mail_encode_address() has been deprecated with message: User the Mailer:: class instead

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
160
    $header .= mail_encode_address($from,'From');
0 ignored issues
show
Deprecated Code introduced by
The function mail_encode_address() has been deprecated with message: User the Mailer:: class instead

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
161
    $header .= mail_encode_address($cc,'Cc');
0 ignored issues
show
Deprecated Code introduced by
The function mail_encode_address() has been deprecated with message: User the Mailer:: class instead

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
162
    $header .= mail_encode_address($bcc,'Bcc');
0 ignored issues
show
Deprecated Code introduced by
The function mail_encode_address() has been deprecated with message: User the Mailer:: class instead

This function has been deprecated. The supplier of the file has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed from the class and what other function to use instead.

Loading history...
163
    $header .= 'MIME-Version: 1.0'.MAILHEADER_EOL;
164
    $header .= 'Content-Type: text/plain; charset=UTF-8'.MAILHEADER_EOL;
165
    $header .= 'Content-Transfer-Encoding: quoted-printable'.MAILHEADER_EOL;
166
    $header .= $headers;
167
    $header  = trim($header);
168
169
    $body = mail_quotedprintable_encode($body);
170
171
    if($params == null){
172
        return @mail($to,$subject,$body,$header);
173
    }else{
174
        return @mail($to,$subject,$body,$header,$params);
175
    }
176
}
177
178
/**
179
 * Encodes an email address header
180
 *
181
 * Unicode characters will be deaccented and encoded
182
 * quoted_printable for headers.
183
 * Addresses may not contain Non-ASCII data!
184
 *
185
 * Example:
186
 *   mail_encode_address("föö <[email protected]>, [email protected]","TBcc");
187
 *
188
 * @param string  $string Multiple adresses separated by commas
189
 * @param string  $header Name of the header (To,Bcc,Cc,...)
190
 * @param boolean $names  Allow named Recipients?
191
 *
192
 * @deprecated User the Mailer:: class instead
193
 */
194
function mail_encode_address($string,$header='',$names=true){
195
    dbg_deprecated('class Mailer::');
196
    $headers = '';
197
    $parts = explode(',',$string);
198
    foreach ($parts as $part){
199
        $part = trim($part);
200
201
        // parse address
202
        if(preg_match('#(.*?)<(.*?)>#',$part,$matches)){
203
            $text = trim($matches[1]);
204
            $addr = $matches[2];
205
        }else{
206
            $addr = $part;
207
        }
208
209
        // skip empty ones
210
        if(empty($addr)){
211
            continue;
212
        }
213
214
        // FIXME: is there a way to encode the localpart of a emailaddress?
215
        if(!\dokuwiki\Utf8\Clean::isASCII($addr)){
216
            msg(hsc("E-Mail address <$addr> is not ASCII"),-1);
217
            continue;
218
        }
219
220
        if(!mail_isvalid($addr)){
221
            msg(hsc("E-Mail address <$addr> is not valid"),-1);
222
            continue;
223
        }
224
225
        // text was given
226
        if(!empty($text) && $names){
227
            // add address quotes
228
            $addr = "<$addr>";
229
230
            if(defined('MAILHEADER_ASCIIONLY')){
231
                $text = \dokuwiki\Utf8\Clean::deaccent($text);
232
                $text = \dokuwiki\Utf8\Clean::strip($text);
233
            }
234
235
            if(!\dokuwiki\Utf8\Clean::isASCII($text)){
236
                // put the quotes outside as in =?UTF-8?Q?"Elan Ruusam=C3=A4e"?= vs "=?UTF-8?Q?Elan Ruusam=C3=A4e?="
237
                if (preg_match('/^"(.+)"$/', $text, $matches)) {
238
                    $text = '"=?UTF-8?Q?'.mail_quotedprintable_encode($matches[1], 0).'?="';
239
                } else {
240
                    $text = '=?UTF-8?Q?'.mail_quotedprintable_encode($text, 0).'?=';
241
                }
242
                // additionally the space character should be encoded as =20 (or each
243
                // word QP encoded separately).
244
                // however this is needed only in mail headers, not globally in mail_quotedprintable_encode().
245
                $text = str_replace(" ", "=20", $text);
246
            }
247
        }else{
248
            $text = '';
249
        }
250
251
        // add to header comma seperated
252
        if($headers != ''){
253
            $headers .= ',';
254
            if($header) $headers .= MAILHEADER_EOL.' '; // avoid overlong mail headers
255
        }
256
        $headers .= $text.' '.$addr;
257
    }
258
259
    if(empty($headers)) return null;
260
261
    //if headername was given add it and close correctly
262
    if($header) $headers = $header.': '.$headers.MAILHEADER_EOL;
263
264
    return $headers;
265
}
266
267
/**
268
 * Check if a given mail address is valid
269
 *
270
 * @param   string $email the address to check
271
 * @return  bool          true if address is valid
272
 */
273
function mail_isvalid($email) {
274
    return EmailAddressValidator::checkEmailAddress($email, true);
275
}
276
277
/**
278
 * Quoted printable encoding
279
 *
280
 * @author umu <umuAThrz.tu-chemnitz.de>
281
 * @link   http://php.net/manual/en/function.imap-8bit.php#61216
282
 *
283
 * @param string $sText
284
 * @param int $maxlen
285
 * @param bool $bEmulate_imap_8bit
286
 *
287
 * @return string
288
 */
289
function mail_quotedprintable_encode($sText,$maxlen=74,$bEmulate_imap_8bit=true) {
290
    // split text into lines
291
    $aLines= preg_split("/(?:\r\n|\r|\n)/", $sText);
292
    $cnt = count($aLines);
293
294
    for ($i=0;$i<$cnt;$i++) {
295
        $sLine =& $aLines[$i];
296
        if (strlen($sLine)===0) continue; // do nothing, if empty
297
298
        $sRegExp = '/[^\x09\x20\x21-\x3C\x3E-\x7E]/e';
299
300
        // imap_8bit encodes x09 everywhere, not only at lineends,
301
        // for EBCDIC safeness encode !"#$@[\]^`{|}~,
302
        // for complete safeness encode every character :)
303
        if ($bEmulate_imap_8bit)
304
            $sRegExp = '/[^\x20\x21-\x3C\x3E-\x7E]/';
305
306
        $sLine = preg_replace_callback( $sRegExp, 'mail_quotedprintable_encode_callback', $sLine );
307
308
        // encode x09,x20 at lineends
309
        {
310
            $iLength = strlen($sLine);
311
            $iLastChar = ord($sLine[$iLength-1]);
312
313
            //              !!!!!!!!
314
            // imap_8_bit does not encode x20 at the very end of a text,
315
            // here is, where I don't agree with imap_8_bit,
316
            // please correct me, if I'm wrong,
317
            // or comment next line for RFC2045 conformance, if you like
318
            if (!($bEmulate_imap_8bit && ($i==count($aLines)-1))){
319
                if (($iLastChar==0x09)||($iLastChar==0x20)) {
320
                    $sLine[$iLength-1]='=';
321
                    $sLine .= ($iLastChar==0x09)?'09':'20';
322
                }
323
            }
324
        }    // imap_8bit encodes x20 before chr(13), too
325
        // although IMHO not requested by RFC2045, why not do it safer :)
326
        // and why not encode any x20 around chr(10) or chr(13)
327
        if ($bEmulate_imap_8bit) {
328
            $sLine=str_replace(' =0D','=20=0D',$sLine);
329
            //$sLine=str_replace(' =0A','=20=0A',$sLine);
330
            //$sLine=str_replace('=0D ','=0D=20',$sLine);
331
            //$sLine=str_replace('=0A ','=0A=20',$sLine);
332
        }
333
334
        // finally split into softlines no longer than $maxlen chars,
335
        // for even more safeness one could encode x09,x20
336
        // at the very first character of the line
337
        // and after soft linebreaks, as well,
338
        // but this wouldn't be caught by such an easy RegExp
339
        if($maxlen){
340
            preg_match_all( '/.{1,'.($maxlen - 2).'}([^=]{0,2})?/', $sLine, $aMatch );
341
            $sLine = implode( '=' . MAILHEADER_EOL, $aMatch[0] ); // add soft crlf's
342
        }
343
    }
344
345
    // join lines into text
346
    return implode(MAILHEADER_EOL,$aLines);
347
}
348
349
function mail_quotedprintable_encode_callback($matches){
350
    return sprintf( "=%02X", ord ( $matches[0] ) ) ;
351
}
352