Browse Source

Exceptions, Mail, Auth, Register, UserMapper, Validator

Visman 8 years ago
parent
commit
35c9d5583c

+ 9 - 0
app/Core/Exceptions/ForkException.php

@@ -0,0 +1,9 @@
+<?php
+
+namespace ForkBB\Core\Exceptions;
+
+use Exception;
+
+class ForkException extends Exception
+{
+}

+ 7 - 0
app/Core/Exceptions/MailException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace ForkBB\Core\Exceptions;
+
+class MailException extends ForkException
+{
+}

+ 7 - 0
app/Core/Exceptions/SmtpException.php

@@ -0,0 +1,7 @@
+<?php
+
+namespace ForkBB\Core\Exceptions;
+
+class StmpException extends MailException
+{
+}

+ 68 - 60
app/Core/Mail.php

@@ -2,7 +2,8 @@
 
 namespace ForkBB\Core;
 
-use RuntimeException;
+use ForkBB\Core\Exceptions\MailException;
+use ForkBB\Core\Exceptions\StmpException;
 
 class Mail
 {
@@ -81,14 +82,33 @@ class Mail
     /**
      * Валидация email
      * @param mixed $email
-     * @return bool
+     * @param bool $strict
+     * @param bool $idna
+     * @return false|string
      */
-    public function valid($email)
+    public function valid($email, $strict = false, $idna = false)
     {
-        return is_string($email)
-            && strlen($email) <= 80
-            && trim($email) === $email
-            && preg_match('%^.+@.+$%D', $email);
+        if (! is_string($email)
+            || mb_strlen($email, 'UTF-8') > 80
+            || ! preg_match('%^([\w!#$\%&\'*+-/=?^`{|}~]+(?:\.[\w!#$\%&\'*+-/=?^`{|}~]+)*)@([^\x00-\x20]+)$%D', $email, $matches)
+        ) {
+            return false;
+        }
+        $local = $matches[1];
+        $domain = mb_strtolower($matches[2], 'UTF-8');
+        if (! preg_match('%^(?:[\p{L}\p{N}]+(?:\-[\p{L}\p{N}]+)*\.)*\p{L}+$%u', $domain)) {
+            return false;
+        }
+
+        $domainASCII = idn_to_ascii($domain);
+
+        if ($strict) {
+            $mx = dns_get_record($domainASCII, DNS_MX);
+            if (empty($mx)) {
+                return false;
+            }
+        }
+        return $local . '@' . ($idna ? $domainASCII : $domain);
     }
 
     /**
@@ -127,8 +147,8 @@ class Mail
             $email = preg_split('%[,\n\r]%', (string) $email, -1, PREG_SPLIT_NO_EMPTY);
         }
         foreach($email as $cur) {
-            $cur = trim((string) $cur);
-            if ($this->valid($cur)) {
+            $cur = $this->valid(trim((string) $cur), false, true);
+            if (false !== $cur) {
                 $this->to[] = $this->formatAddress($cur, $name);
             }
         }
@@ -155,7 +175,8 @@ class Mail
      */
     public function setFrom($email, $name = null)
     {
-        if ($this->valid($email)) {
+        $email = $this->valid($email, false, true);
+        if (false !== $email) {
             $this->headers['From'] = $this->formatAddress($email, $name);
         }
         return $this;
@@ -169,7 +190,8 @@ class Mail
      */
     public function setReplyTo($email, $name = null)
     {
-        if ($this->valid($email)) {
+        $email = $this->valid($email, false, true);
+        if (false !== $email) {
             $this->headers['Reply-To'] = $this->formatAddress($email, $name);
         }
         return $this;
@@ -183,8 +205,7 @@ class Mail
      */
     protected function formatAddress($email, $name = null)
     {
-        $email = $this->filterEmail($email);
-        if (null === $name || ! is_string($name) || strlen(trim($name)) == 0) {
+        if (! is_string($name) || strlen(trim($name)) == 0) {
             return $email;
         } else {
             $name = $this->encodeText($this->filterName($name));
@@ -206,16 +227,6 @@ class Mail
         }
     }
 
-    /**
-     * Фильтрация email
-     * @param string $email
-     * @return string
-     */
-    protected function filterEmail($email)
-    {
-        return preg_replace('%[\x00-\x1F",<>]%', '', $email);
-    }
-
     /**
      * Фильтрация имени
      * @param string $name
@@ -223,14 +234,7 @@ class Mail
      */
     protected function filterName($name)
     {
-        return strtr(trim($name), [
-            "\r" => '',
-            "\n" => '',
-            "\t" => '',
-            '"'  => '\'',
-            '<'  => '[',
-            '>'  => ']',
-        ]);
+        return addcslashes(preg_replace('%[\x00-\x1F]%', '', trim($name)), '\\"');
     }
 
     /**
@@ -259,14 +263,14 @@ class Mail
      * Задает сообщение по шаблону
      * @param string $tpl
      * @param array $data
-     * @throws \RuntimeException
+     * @throws MailException
      * @return Mail
      */
     public function setTpl($tpl, array $data)
     {
         $file = rtrim($this->folder, '\\/') . '/' . $this->language . '/mail/' . $tpl;
         if (! file_exists($file)) {
-            throw new RuntimeException('Tpl not found');
+            throw new MailException('The template isn\'t found (' . $file . ').');
         }
         $tpl = trim(file_get_contents($file));
         foreach ($data as $key => $val) {
@@ -274,7 +278,7 @@ class Mail
         }
         list($subject, $tpl) = explode("\n", $tpl, 2);
         if (! isset($tpl)) {
-            throw new RuntimeException('Tpl empty');
+            throw new MailException('The template is empty (' . $file . ').');
         }
         $this->setSubject(substr($subject, 8));
         return $this->setMessage($tpl);
@@ -283,7 +287,6 @@ class Mail
     /**
      * Задает сообщение
      * @param string $message
-     * @throws \RuntimeException
      * @return Mail
      */
     public function setMessage($message)
@@ -297,21 +300,22 @@ class Mail
 
     /**
      * Отправляет письмо
+     * @throws MailException
      * @return bool
      */
     public function send()
     {
         if (empty($this->to)) {
-            throw new RuntimeException('No recipient(s)');
+            throw new MailException('No recipient for the email.');
         }
         if (empty($this->headers['From'])) {
-            throw new RuntimeException('No sender');
+            throw new MailException('No sender for the email.');
         }
         if (! isset($this->headers['Subject'])) {
-            throw new RuntimeException('Subject empty');
+            throw new MailException('The subject of the email is empty.');
         }
         if (trim($this->message) == '') {
-            throw new RuntimeException('Message empty');
+            throw new MailException('The body of the email is empty.');
         }
 
         $this->headers = array_replace($this->headers, [
@@ -359,7 +363,7 @@ class Mail
 
     /**
      * Отправка письма через smtp
-     * @throws \RuntimeException
+     * @throws SmtpException
      * @return bool
      */
     protected function smtp()
@@ -367,7 +371,7 @@ class Mail
         // подлючение
         if (! is_resource($this->connect)) {
             if (($connect = @fsockopen($this->smtp['host'], $this->smtp['port'], $errno, $errstr, 5)) === false) {
-                throw new RuntimeException('Could not connect to smtp host "' . $this->smtp['host'] . '" (' . $errno . ') (' . $errstr . ')');
+                throw new SmtpException('Couldn\'t connect to smtp host "' . $this->smtp['host'] . ':' . $this->smtp['port'] . '" (' . $errno . ') (' . $errstr . ').');
             }
             stream_set_timeout($connect, 5);
             $this->connect = $connect;
@@ -389,15 +393,6 @@ class Mail
         return true;
     }
 
-    public function __destruct()
-    {
-        // завершение сеанса smtp
-        if (is_resource($this->connect)) {
-            $this->smtpData('QUIT', null);
-            @fclose($this->connect);
-        }
-    }
-
     /**
      * Hello SMTP server
      */
@@ -425,23 +420,25 @@ class Mail
     }
 
     /**
+     * Отправляет данные на сервер
+     * Проверяет ответ
+     * Возвращает код ответа
      * @param string $data
      * @param mixed $code
-     * @throws \RuntimeException
+     * @throws SmtpException
      * @return string
      */
     protected function smtpData($data, $code)
     {
-//var_dump($data);
-        if (is_string($data)) {
-            @fwrite($this->connect, $data . $this->EOL);
+        if (is_resource($this->connect) && is_string($data)) {
+            if (@fwrite($this->connect, $data . $this->EOL) === false) {
+                throw new SmtpException('Couldn\'t send data to mail server.');
+            }
         }
-
         $response = '';
-//        while (! isset($get{3}) || $get{3} !== ' ') {
-        while (is_resource($this->connect) && !feof($this->connect)) {
+        while (is_resource($this->connect) && ! feof($this->connect)) {
             if (($get = @fgets($this->connect, 512)) === false) {
-                throw new RuntimeException('Couldn\'t get mail server response codes');
+                throw new SmtpException('Couldn\'t get mail server response codes.');
             }
             $response .= $get;
             if (isset($get{3}) && $get{3} === ' ') {
@@ -449,9 +446,8 @@ class Mail
                 break;
             }
         }
-//var_dump($response);
         if ($code !== null && ! in_array($return, (array) $code)) {
-            throw new RuntimeException('Unable to send email. Response of the SMTP server: "'.$get.'"');
+            throw new SmtpException('Unable to send email. Response of mail server: "' . $get . '"');
         }
         return $return;
     }
@@ -481,4 +477,16 @@ class Mail
             ? (isset($_SERVER['SERVER_ADDR']) ? '[' . $_SERVER['SERVER_ADDR'] . ']' : '[127.0.0.1]')
             : $_SERVER['SERVER_NAME'];
     }
+
+    /**
+     * Деструктор
+     */
+    public function __destruct()
+    {
+        // завершение сеанса smtp
+        if (is_resource($this->connect)) {
+            $this->smtpData('QUIT', null);
+            @fclose($this->connect);
+        }
+    }
 }

+ 23 - 11
app/Models/Pages/Auth.php

@@ -2,8 +2,9 @@
 
 namespace ForkBB\Models\Pages;
 
-use ForkBB\Models\Validator;
+use ForkBB\Core\Exceptions\MailException;
 use ForkBB\Models\User;
+use ForkBB\Models\Validator;
 
 class Auth extends Page
 {
@@ -226,14 +227,21 @@ class Auth extends Page
             'username' => $this->tmpUser->username,
             'link' => $link,
         ];
-        $mail = $this->c->Mail->reset()
-            ->setFolder($this->c->DIR_LANG)
-            ->setLanguage($this->tmpUser->language)
-            ->setTo($v->email, $this->tmpUser->username)
-            ->setFrom($this->config['o_webmaster_email'], __('Mailer', $this->config['o_board_title']))
-            ->setTpl('password_reset.tpl', $tplData);
-
-        if ($mail->send()) {
+
+        try {
+            $isSent = $this->c->Mail
+                ->reset()
+                ->setFolder($this->c->DIR_LANG)
+                ->setLanguage($this->tmpUser->language)
+                ->setTo($v->email, $this->tmpUser->username)
+                ->setFrom($this->config['o_webmaster_email'], __('Mailer', $this->config['o_board_title']))
+                ->setTpl('password_reset.tpl', $tplData)
+                ->send();
+        } catch (MailException $e) {
+            $isSent = false;
+        }
+
+        if ($isSent) {
             $this->c->UserMapper->updateUser($this->tmpUser->id, ['activate_string' => $key, 'last_email_sent' => time()]);
             return $this->c->Message->message(__('Forget mail', $this->config['o_admin_email']), false, 200);
         } else {
@@ -283,7 +291,6 @@ class Auth extends Page
         } else {
             // что-то пошло не так
             if (! hash_equals($args['hash'], $this->c->Secury->hash($args['email'] . $args['key']))
-                || ! $this->c->Mail->valid($args['email'])
                 || ! ($user = $this->c->UserMapper->getUser($args['email'], 'email')) instanceof User
                 || empty($user->activateString)
                 || $user->activateString{0} !== 'p'
@@ -295,6 +302,12 @@ class Auth extends Page
 
         $this->c->Lang->load('auth');
 
+        if ($user->isUnverified) {
+            $this->c->UserMapper->updateUser($user->id, ['group_id' => $this->config['o_default_user_group'], 'email_confirmed' => 1]);
+            $this->c->{'users_info update'};
+            $this->iswev['i'][] = __('Account activated');
+        }
+
         $this->titles = [
             __('Change pass'),
         ];
@@ -315,7 +328,6 @@ class Auth extends Page
     {
         // что-то пошло не так
         if (! hash_equals($args['hash'], $this->c->Secury->hash($args['email'] . $args['key']))
-            || ! $this->c->Mail->valid($args['email'])
             || ! ($user = $this->c->UserMapper->getUser($args['email'], 'email')) instanceof User
             || empty($user->activateString)
             || $user->activateString{0} !== 'p'

+ 31 - 19
app/Models/Pages/Register.php

@@ -2,8 +2,9 @@
 
 namespace ForkBB\Models\Pages;
 
-use ForkBB\Models\Validator;
+use ForkBB\Core\Exceptions\MailException;
 use ForkBB\Models\User;
+use ForkBB\Models\Validator;
 
 class Register extends Page
 {
@@ -134,11 +135,9 @@ class Register extends Page
         if ($this->config['o_regs_verify'] == '1') {
             $groupId = PUN_UNVERIFIED;
             $key = 'w' . $this->c->Secury->randomPass(79);
-            $visit = 0;
         } else {
             $groupId = $this->config['o_default_user_group'];
             $key = null;
-            $visit = time(); //????
         }
 
         $newUserId = $this->c->UserMapper->newUser(new User([
@@ -147,8 +146,8 @@ class Register extends Page
             'password' => password_hash($v->password, PASSWORD_DEFAULT),
             'email' => $v->email,
             'email_confirmed' => 0,
-            'last_visit' => $visit,
             'activate_string' => $key,
+            'u_mark_all_read' => time(),
         ], $this->c));
 
         // обновление статистики по пользователям
@@ -157,7 +156,7 @@ class Register extends Page
         }
 
         // уведомление о регистрации
-        if ($this->config['o_mailing_list'] != '' && $this->config['o_regs_report'] == '1') {
+        if ($this->config['o_regs_report'] == '1' && $this->config['o_mailing_list'] != '') {
             $tplData = [
                 'fTitle' => $this->config['o_board_title'],
                 'fRootLink' => $this->c->Router->link('Index'),
@@ -165,13 +164,19 @@ class Register extends Page
                 'username' => $v->username,
                 'userLink' => $this->c->Router->link('User', ['id' => $newUserId, 'name' => $v->username]),
             ];
-            $mail = $this->c->Mail->reset()
-                ->setFolder($this->c->DIR_LANG)
-                ->setLanguage($this->config['o_default_lang'])
-                ->setTo($this->config['o_mailing_list'])
-                ->setFrom($this->config['o_webmaster_email'], __('Mailer', $this->config['o_board_title']))
-                ->setTpl('new_user.tpl', $tplData)
-                ->send();
+
+            try {
+                $this->c->Mail
+                    ->reset()
+                    ->setFolder($this->c->DIR_LANG)
+                    ->setLanguage($this->config['o_default_lang'])
+                    ->setTo($this->config['o_mailing_list'])
+                    ->setFrom($this->config['o_webmaster_email'], __('Mailer', $this->config['o_board_title']))
+                    ->setTpl('new_user.tpl', $tplData)
+                    ->send();
+            } catch (MailException $e) {
+            //????
+            }
         }
 
         $this->c->Lang->load('register');
@@ -187,15 +192,22 @@ class Register extends Page
                 'username' => $v->username,
                 'link' => $link,
             ];
-            $mail = $this->c->Mail->reset()
-                ->setFolder($this->c->DIR_LANG)
-                ->setLanguage($this->c->user->language)
-                ->setTo($v->email)
-                ->setFrom($this->config['o_webmaster_email'], __('Mailer', $this->config['o_board_title']))
-                ->setTpl('welcome.tpl', $tplData);
+
+            try {
+                $isSent = $this->c->Mail
+                    ->reset()
+                    ->setFolder($this->c->DIR_LANG)
+                    ->setLanguage($this->c->user->language)
+                    ->setTo($v->email)
+                    ->setFrom($this->config['o_webmaster_email'], __('Mailer', $this->config['o_board_title']))
+                    ->setTpl('welcome.tpl', $tplData)
+                    ->send();
+            } catch (MailException $e) {
+                $isSent = false;
+            }
 
             // письмо активации аккаунта отправлено
-            if ($mail->send()) {
+            if ($isSent) {
                 return $this->c->Message->message(__('Reg email', $this->config['o_admin_email']), false, 200);
             // форма сброса пароля
             } else {

+ 2 - 2
app/Models/UserMapper.php

@@ -2,8 +2,8 @@
 
 namespace ForkBB\Models;
 
-use ForkBB\Models\User;
 use ForkBB\Core\Container;
+use ForkBB\Models\User;
 use RuntimeException;
 use InvalidArgumentException;
 
@@ -172,7 +172,7 @@ class UserMapper
      */
     public function newUser(User $user)
     {
-        $this->db->query('INSERT INTO '.$this->db->prefix.'users (username, group_id, password, email, email_confirmed, email_setting, timezone, dst, language, style, registered, registration_ip, last_visit, activate_string) VALUES(\''.$this->db->escape($user->username).'\', '.$user->groupId.', \''.$this->db->escape($user->password).'\', \''.$this->db->escape($user->email).'\', '.$user->emailConfirmed.', '.$this->config['o_default_email_setting'].', '.$this->config['o_default_timezone'].' , '.$this->config['o_default_dst'].', \''.$this->db->escape($user->language).'\', \''.$user->style.'\', '.time().', \''.$this->db->escape($this->getIpAddress()).'\', '.$user->lastVisit.', \''.$this->db->escape($user->activateString).'\')') or error('Unable to create user', __FILE__, __LINE__, $this->db->error());
+        $this->db->query('INSERT INTO '.$this->db->prefix.'users (username, group_id, password, email, email_confirmed, email_setting, timezone, dst, language, style, registered, registration_ip, activate_string, u_mark_all_read) VALUES(\''.$this->db->escape($user->username).'\', '.$user->groupId.', \''.$this->db->escape($user->password).'\', \''.$this->db->escape($user->email).'\', '.$user->emailConfirmed.', '.$this->config['o_default_email_setting'].', '.$this->config['o_default_timezone'].' , '.$this->config['o_default_dst'].', \''.$this->db->escape($user->language).'\', \''.$user->style.'\', '.time().', \''.$this->db->escape($this->getIpAddress()).'\', \''.$this->db->escape($user->activateString).'\', '.$user->uMarkAllRead.')') or error('Unable to create user', __FILE__, __LINE__, $this->db->error());
         $new_uid = $this->db->insert_id(); //????
         return $new_uid;
     }

+ 5 - 6
app/Models/Validator.php

@@ -491,13 +491,12 @@ class Validator
     {
         if (null === $value) {
             return [$value, $type, false];
-        } elseif ($this->c->Mail->valid($value)) {
-            return [$value, $type, false];
+        }
+        $email = $this->c->Mail->valid($value, true);
+        if (false === $email) {
+            return [(string) $value, $type, 'The :alias is not valid email'];
         } else {
-            if (! is_string($value)) {
-                $value = (string) $value;
-            }
-            return [$value, $type, 'The :alias is not valid email'];
+            return [$email, $type, false];
         }
     }
 

+ 3 - 0
app/lang/English/auth.po

@@ -74,3 +74,6 @@ msgstr "Passwords must be at least 8 characters long. Passwords are case sensiti
 
 msgid "Pass updated"
 msgstr "Your password has been updated. You can now login with your new password."
+
+msgid "Account activated"
+msgstr "Account activated."

+ 1 - 1
app/lang/English/register.po

@@ -67,4 +67,4 @@ msgid "Login format"
 msgstr "The username must begin with a letter. May contain letters, numbers, spaces, dots, dashes and underscores."
 
 msgid "Error welcom mail"
-msgstr "When sending email there was an error. Please use the password reset form or contact the forum administrator at <a href=\"mailto:%1$s\">%1$s</a>."
+msgstr "When sending email there was an error. Please use the password reset form for activate your account or contact the forum administrator at <a href=\"mailto:%1$s\">%1$s</a>."

+ 3 - 0
app/lang/Russian/auth.po

@@ -74,3 +74,6 @@ msgstr "Пароль должен состоять минимум из 8 сим
 
 msgid "Pass updated"
 msgstr "Ваш пароль изменён. Вы можете войти на форум с новым паролем."
+
+msgid "Account activated"
+msgstr "Аккаунт активирован."

+ 1 - 1
app/lang/Russian/register.po

@@ -67,4 +67,4 @@ msgid "Login format"
 msgstr "Имя пользователя должно начинаться с буквы. Может содержать буквы, цифры, пробел, точку, дефис и знак подчеркивания."
 
 msgid "Error welcom mail"
-msgstr "При отправке письма возникла ошибка. Пожалуйста, воспользуйтесь формой восстановления пароля или свяжитесь с администратором форума по адресу <a href=\"mailto:%1$s\">%1$s</a>."
+msgstr "При отправке письма возникла ошибка. Пожалуйста, воспользуйтесь формой восстановления пароля для активации аккаунта или свяжитесь с администратором форума по адресу <a href=\"mailto:%1$s\">%1$s</a>."