1 /*
2  * Copyright (C) 2017-2018 Matthias Fehring <kontakt@buschmann23.de>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 #ifndef CUTELYSTVALIDATOREMAIL_H
19 #define CUTELYSTVALIDATOREMAIL_H
20 
21 #include <Cutelyst/cutelyst_global.h>
22 #include "validatorrule.h"
23 
24 namespace Cutelyst {
25 
26 class ValidatorEmailPrivate;
27 
28 /*!
29  * \ingroup plugins-utils-validator-rules
30  * \class ValidatorEmail validatoremail.h <Cutelyst/Plugins/Utils/validatoremail.h>
31  * \brief Checks if the value is a valid email address according to specific RFCs.
32  *
33  * You can use a \link ValidatorEmail::Category Category\endlink as threshold to define which level of compliance you accpet as valid.
34  * The default threshold RFC5321 for example will only allow email addresses that can be sent without modification through SMTP. If
35  * the address would contain comments like <code>(main address)test\@example.com</code>, it would not be valid because the \link ValidatorEmail::CFWSComment
36  * CFWSComment\endlink diagnose is above the threshold. If it would contain a quoted string like <code>"main test address"\@example.com</code> it would be
37  * valid because the \link ValidatorEmail::RFC531QuotedString RFC5321QuotedString\endlink diagnose is under the threshold.
38  *
39  * The parser used to validate the email address is a reimplementation of Dominic Sayers’ <a href="https://github.com/dominicsayers/isemail">isemail</a> PHP parser.
40  *
41  * \note Unless \link Validator::validate() validation\endlink is started with \link Validator::NoTrimming NoTrimming\endlink,
42  * whitespaces will be removed from the beginning and the end of the input value before validation.
43  * If the \a field's value is empty or if the \a field is missing in the input data, the validation will succeed without
44  * performing the validation itself. Use one of the \link ValidatorRequired required validators \endlink to require the
45  * field to be present and not empty.
46  *
47  * \sa Validator for general usage of validators.
48  */
49 class CUTELYST_PLUGIN_UTILS_VALIDATOR_EXPORT ValidatorEmail : public ValidatorRule
50 {
51     Q_GADGET
52 public:
53     /*!
54      * \brief Validation category, used as threshold to define valid addresses.
55      */
56     enum Category : quint8 {
57         Valid           = 1,    /**< Address is completely valid. */
58         DNSWarn         = 7,    /**< Address is valid but a DNS check was not successful. Diagnose in this category is only returned if \a checkDns ist set to \c true. */
59         RFC5321         = 15,   /**< Address is valid for SMTP according to <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a> but has unusual Elements. */
60         CFWS            = 31,   /**< Address is valid within the message but can not be used unmodified for the envelope. */
61         Deprecated      = 63,   /**< Address contains deprecated elements but may still be valid in restricted contexts. */
62         RFC5322         = 127,  /**< Address is only valid according to the broad definition of <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a>. It is otherwise invalid. */
63         Error           = 255   /**< Address is invalid for any purpose. */
64     };
65     Q_ENUM(Category)
66 
67     /*!
68      * \brief Single diagnose values that show why an address is not valid.
69      */
70     enum Diagnose : quint8 {
71         // Address is valid
72         ValidAddress            = 0,    /**< Address is valid. Please note that this does not mean the address actually exists, nor even that the domain actually exists. This address could be issued by the domain owner without breaking the rules of any RFCs. */
73         // Address is valid but a DNS check was not successful
74         DnsWarnNoMxRecord       = 5,    /**< Couldn't find a MX record for this domain but an A-record does exist. */
75         DnsWarnNoRecord         = 6,    /**< Could neither find a MX record nor an A-record for this domain. */
76         // Address is valid for SMTP but has unusual Elements
77         RFC5321TLD              = 9,    /**< Address is valid but at a Top Level Domain. */
78         RFC5321TLDNumberic      = 10,   /**< Address is valid but the Top Level Domain begins with a number. */
79         RFC5321QuotedString     = 11,   /**< Address is valid but contains a quoted string. */
80         RFC5321AddressLiteral   = 12,   /**< Address is valid but a literal address not a domain. */
81         RFC5321IPv6Deprecated   = 13,   /**< Address is valid but contains a :: that only elides one zero group. All implementations must accept and be able to handle any legitimate <a href="https://tools.ietf.org/html/rfc4291">RFC 4291</a> format. */
82         // Address is valid within the message but cannot be used unmodified for the envelope
83         CFWSComment             = 17,   /**< Address contains comments. */
84         CFWSFWS                 = 18,   /**< Address contains Folding White Space. */
85         // Address contains deprecated elements but may still be valid in restricted contexts
86         DeprecatedLocalpart     = 33,   /**< The local part is in a deprecated form. */
87         DeprecatedFWS           = 34,   /**< Address contains an obsolete form of Folding White Space. */
88         DeprecatedQText         = 35,   /**< A quoted string contains a deprecated character. */
89         DeprecatedQP            = 36,   /**< A quoted pair contains a deprecated character. */
90         DeprecatedComment       = 37,   /**< Address contains a comment in a position that is deprecated. */
91         DeprecatedCText         = 38,   /**< A comment contains a deprecated character. */
92         DeprecatedCFWSNearAt    = 49,   /**< Address contains a comment or Folding White Space around the @ sign. */
93         // The address in only valid according to the broad definition of RFC 5322. It is otherwise invalid
94         RFC5322Domain           = 65,   /**< Address is <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a> compliant but contains domain characters that are not allowed by DNS. */
95         RFC5322TooLong          = 66,   /**< Address is too long. */
96         RFC5322LocalTooLong     = 67,   /**< The local part of the address is too long. */
97         RFC5322DomainTooLong    = 68,   /**< The domain part is too long. */
98         RFC5322LabelTooLong     = 69,   /**< The domain part contains an element that is too long. */
99         RFC5322DomainLiteral    = 70,   /**< The domain literal is not a valid <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a> address literal. */
100         RFC5322DomLitOBSDText   = 71,   /**< The domain literal is not a valid <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a> address literal and it contains obsolete characters. */
101         RFC5322IPv6GroupCount   = 72,   /**< The IPv6 literal address contains the wrong number of groups. */
102         RFC5322IPv62x2xColon    = 73,   /**< The IPv6 literal address contains too many :: sequences. */
103         RFC5322IPv6BadChar      = 74,   /**< The IPv6 address contains an illegal group of characters. */
104         RFC5322IPv6MaxGroups    = 75,   /**< The IPv6 address has too many groups. */
105         RFC5322IPv6ColonStart   = 76,   /**< IPv6 address starts with a single colon. */
106         RFC5322IPv6ColonEnd     = 77,   /**< IPv6 address ends with a single colon. */
107         // Address is invalid for any purpose
108         ErrorExpectingDText     = 129,  /**< A domain literal contains a character that is not allowed. */
109         ErrorNoLocalPart        = 130,  /**< Address has no local part. */
110         ErrorNoDomain           = 131,  /**< Address has no domain part. */
111         ErrorConsecutiveDots    = 132,  /**< The address may not contain consecutive dots. */
112         ErrorATextAfterCFWS     = 133,  /**< Address contains text after a comment or Folding White Space. */
113         ErrorATextAfterQS       = 134,  /**< Address contains text after a quoted string. */
114         ErrorATextAfterDomLit   = 135,  /**< Extra characters were found after the end of the domain literal. */
115         ErrorExpectingQpair     = 136,  /**< The address contains a character that is not allowed in a quoted pair. */
116         ErrorExpectingAText     = 137,  /**< Address contains a character that is not allowed. */
117         ErrorExpectingQText     = 138,  /**< A quoted string contains a character that is not allowed. */
118         ErrorExpectingCText     = 139,  /**< A comment contains a character that is not allowed. */
119         ErrorBackslashEnd       = 140,  /**< The address can't end with a backslash. */
120         ErrorDotStart           = 141,  /**< Neither part of the address may begin with a dot. */
121         ErrorDotEnd             = 142,  /**< Neither part of the address may end with a dot. */
122         ErrorDomainHyphenStart  = 143,  /**< A domain or subdomain cannot begin with a hyphen. */
123         ErrorDomainHyphenEnd    = 144,  /**< A domain or subdomain cannot end with a hyphen. */
124         ErrorUnclosedQuotedStr  = 145,  /**< Unclosed quoted string. */
125         ErrorUnclosedComment    = 146,  /**< Unclosed comment. */
126         ErrorUnclosedDomLiteral = 147,  /**< Domain literal is missing its closing bracket. */
127         ErrorFWSCRLFx2          = 148,  /**< Folding White Space contains consecutive CRLF sequences. */
128         ErrorFWSCRLFEnd         = 149,  /**< Folding White Space ends with a CRLF sequence. */
129         ErrorCRnoLF             = 150,  /**< Address contains a carriage return that is not followed by a line feed. */
130         ErrorFatal              = 254   /**< Fatal internal error while validating the address. */
131     };
132     Q_ENUM(Diagnose)
133 
134     enum Option : quint8 {
135         NoOption    = 0,                    /**< No option enabled, the default. */
136         CheckDNS    = 1,                    /**< Enabled a DNS lookup to check if there are MX records for the mail domain. */
137         UTF8Local   = 2,                    /**< Allows UTF8 characters in the email address local part. */
138         AllowIDN    = 4,                    /**< Allows internationalized domain names (IDN). */
139         AllowUTF8   = UTF8Local|AllowIDN    /**< Allows UTF8 characters in the email local part and internationalized domain names (IDN). */
140     };
141     Q_DECLARE_FLAGS(Options, Option)
142 
143     /*!
144      * \brief Constructs a new email validator.
145      * \param field         Name of the input field to validate.
146      * \param options       Options for the validation process.
147      * \param messages      Custom error messages if validation fails.
148      * \param defValKey     \link Context::stash() Stash \endlink key containing a default value if input field is empty. This value will \b NOT be validated.
149      */
150     ValidatorEmail(const QString &field, Category threshold = RFC5321, Options options = NoOption, const ValidatorMessages &messages = ValidatorMessages(), const QString &defValKey = QString());
151 
152     /*!
153      * \brief Deconstructs the email validator.
154      */
155     ~ValidatorEmail() override;
156 
157     /*!
158      * \brief Returns a descriptive and translated string for the \a diagnose.
159      * \param c         The current Context, used for translation.
160      * \param diagnose  The Diagnose to return the descriptive string for.
161      * \param label     Optional label used in the diagnose string.
162      * \return Descriptive and translated string for the \a diagnose.
163      */
164     static QString diagnoseString(Context *c, Diagnose diagnose, const QString &label = QString());
165 
166     /*!
167      * \brief Returns a descriptive and translated string for the \a category.
168      * \param c         The current Context, used for translation.
169      * \param category  The Category to return the descriptive string for.
170      * \param label     Optional label used in the category string.
171      * \return Descriptive and translated string for the \a category.
172      */
173     static QString categoryString(Context *c, Category category, const QString &label = QString());
174 
175     /*!
176      * \brief Returns the category the \a diagnose belongs to.
177      * \param diagnose  The Diagnose to get the Category for.
178      * \return The Category the \a diagnose belongs to.
179      */
180     static Category category(Diagnose diagnose);
181 
182     /*!
183      * \brief Returns a descriptive and translated string for the Category the \a diagnose belongs to.
184      * \param c         The current context, used for translation.
185      * \param diagnose  The Diagnose to return the descriptive Category string for.
186      * \param label     Optional label used in the category string.
187      * \return Descriptive and translated string for the Category the \a diagnose belongs to.
188      */
189     static QString categoryString(Context *c, Diagnose diagnose, const QString &label = QString());
190 
191     /*!
192      * \ingroup plugins-utils-validator-rules
193      * \brief Returns \c true if \a email is a valid address according to the Category given in the \a threshold.
194      * \param[in] email         The address to validate.
195      * \param[in] threshold     The threshold category that limits the diagnose that is accepted as valid.
196      * \param[in] options       Options for the validation process.
197      * \param[out] diagnoses    If not a \c nullptr, this will contain a list of all issues found by the check, ordered from the highest to the lowest.
198      * \return \c true if \a email is a valid address according to the Category given in the \a threshold.
199      */
200     static bool validate(const QString &email, Category threshold = RFC5321, Options options = NoOption, QList<Diagnose> *diagnoses = nullptr);
201 
202 protected:
203     /*!
204      * \brief Performs the validation and returns the result.
205      *
206      * If validation succeeded, ValidatorReturnType::value will contain the cleaned up email address without any comments as QString.
207      * ValidatorReturnType::extra will contain a QList<Diagnose> list containing all issues found in the checked email, ordered from
208      * the highest to the lowest.
209      */
210     ValidatorReturnType validate(Context *c, const ParamsMultiMap &params) const override;
211 
212     /*!
213      * \brief Returns a generic error if validation failed.
214      */
215     QString genericValidationError(Context *c, const QVariant &errorData = QVariant()) const override;
216 
217 private:
218     Q_DECLARE_PRIVATE(ValidatorEmail)
219     Q_DISABLE_COPY(ValidatorEmail)
220 };
221 
222 }
223 
224 Q_DECLARE_OPERATORS_FOR_FLAGS(Cutelyst::ValidatorEmail::Options)
225 
226 #endif //CUTELYSTVALIDATOREMAIL_H
227 
228