1<?php
2
3namespace Egulias\EmailValidator\Parser;
4
5use Egulias\EmailValidator\Exception\DotAtEnd;
6use Egulias\EmailValidator\Exception\DotAtStart;
7use Egulias\EmailValidator\EmailLexer;
8use Egulias\EmailValidator\Exception\ExpectingAT;
9use Egulias\EmailValidator\Exception\ExpectingATEXT;
10use Egulias\EmailValidator\Exception\UnclosedQuotedString;
11use Egulias\EmailValidator\Exception\UnopenedComment;
12use Egulias\EmailValidator\Warning\CFWSWithFWS;
13use Egulias\EmailValidator\Warning\LocalTooLong;
14
15class LocalPart extends Parser
16{
17    public function parse($localPart)
18    {
19        $parseDQuote = true;
20        $closingQuote = false;
21        $openedParenthesis = 0;
22        $totalLength = 0;
23
24        while ($this->lexer->token['type'] !== EmailLexer::S_AT && null !== $this->lexer->token['type']) {
25            if ($this->lexer->token['type'] === EmailLexer::S_DOT && null === $this->lexer->getPrevious()['type']) {
26                throw new DotAtStart();
27            }
28
29            $closingQuote = $this->checkDQUOTE($closingQuote);
30            if ($closingQuote && $parseDQuote) {
31                $parseDQuote = $this->parseDoubleQuote();
32            }
33
34            if ($this->lexer->token['type'] === EmailLexer::S_OPENPARENTHESIS) {
35                $this->parseComments();
36                $openedParenthesis += $this->getOpenedParenthesis();
37            }
38
39            if ($this->lexer->token['type'] === EmailLexer::S_CLOSEPARENTHESIS) {
40                if ($openedParenthesis === 0) {
41                    throw new UnopenedComment();
42                }
43
44                $openedParenthesis--;
45            }
46
47            $this->checkConsecutiveDots();
48
49            if ($this->lexer->token['type'] === EmailLexer::S_DOT &&
50                $this->lexer->isNextToken(EmailLexer::S_AT)
51            ) {
52                throw new DotAtEnd();
53            }
54
55            $this->warnEscaping();
56            $this->isInvalidToken($this->lexer->token, $closingQuote);
57
58            if ($this->isFWS()) {
59                $this->parseFWS();
60            }
61
62            $totalLength += strlen($this->lexer->token['value']);
63            $this->lexer->moveNext();
64        }
65
66        if ($totalLength > LocalTooLong::LOCAL_PART_LENGTH) {
67            $this->warnings[LocalTooLong::CODE] = new LocalTooLong();
68        }
69    }
70
71    /**
72     * @return bool
73     */
74    protected function parseDoubleQuote()
75    {
76        $parseAgain = true;
77        $special = array(
78            EmailLexer::S_CR => true,
79            EmailLexer::S_HTAB => true,
80            EmailLexer::S_LF => true
81        );
82
83        $invalid = array(
84            EmailLexer::C_NUL => true,
85            EmailLexer::S_HTAB => true,
86            EmailLexer::S_CR => true,
87            EmailLexer::S_LF => true
88        );
89        $setSpecialsWarning = true;
90
91        $this->lexer->moveNext();
92
93        while ($this->lexer->token['type'] !== EmailLexer::S_DQUOTE && null !== $this->lexer->token['type']) {
94            $parseAgain = false;
95            if (isset($special[$this->lexer->token['type']]) && $setSpecialsWarning) {
96                $this->warnings[CFWSWithFWS::CODE] = new CFWSWithFWS();
97                $setSpecialsWarning = false;
98            }
99            if ($this->lexer->token['type'] === EmailLexer::S_BACKSLASH && $this->lexer->isNextToken(EmailLexer::S_DQUOTE)) {
100                $this->lexer->moveNext();
101            }
102
103            $this->lexer->moveNext();
104
105            if (!$this->escaped() && isset($invalid[$this->lexer->token['type']])) {
106                throw new ExpectingATEXT();
107            }
108        }
109
110        $prev = $this->lexer->getPrevious();
111
112        if ($prev['type'] === EmailLexer::S_BACKSLASH) {
113            if (!$this->checkDQUOTE(false)) {
114                throw new UnclosedQuotedString();
115            }
116        }
117
118        if (!$this->lexer->isNextToken(EmailLexer::S_AT) && $prev['type'] !== EmailLexer::S_BACKSLASH) {
119            throw new ExpectingAT();
120        }
121
122        return $parseAgain;
123    }
124
125    /**
126     * @param bool $closingQuote
127     */
128    protected function isInvalidToken(array $token, $closingQuote)
129    {
130        $forbidden = array(
131            EmailLexer::S_COMMA,
132            EmailLexer::S_CLOSEBRACKET,
133            EmailLexer::S_OPENBRACKET,
134            EmailLexer::S_GREATERTHAN,
135            EmailLexer::S_LOWERTHAN,
136            EmailLexer::S_COLON,
137            EmailLexer::S_SEMICOLON,
138            EmailLexer::INVALID
139        );
140
141        if (in_array($token['type'], $forbidden) && !$closingQuote) {
142            throw new ExpectingATEXT();
143        }
144    }
145}
146