1/* Copyright 2016 Software Freedom Conservancy Inc.
2 *
3 * This software is licensed under the GNU Lesser General Public License
4 * (version 2.1 or later).  See the COPYING file in this distribution.
5 */
6
7public class Geary.Smtp.ResponseCode {
8    public const int STRLEN = 3;
9
10    public const int MIN = 100;
11    public const int MAX = 599;
12
13    public const string START_DATA_CODE = "354";
14    public const string STARTTLS_READY_CODE = "220";
15    public const string DENIED_CODE = "550";
16
17    public enum Status {
18        POSITIVE_PRELIMINARY = 1,
19        POSITIVE_COMPLETION = 2,
20        POSITIVE_INTERMEDIATE = 3,
21        TRANSIENT_NEGATIVE = 4,
22        PERMANENT_FAILURE = 5,
23        UNKNOWN = -1;
24    }
25
26    public enum Condition {
27        SYNTAX = 0,
28        ADDITIONAL_INFO = 1,
29        COMM_CHANNEL = 2,
30        MAIL_SYSTEM = 5,
31        UNKNOWN = -1
32    }
33
34    private string str;
35
36    public ResponseCode(string str) throws SmtpError {
37        // these two checks are sufficient to make sure the Status is valid, but not the Condition
38        if (str.length != STRLEN)
39            throw new SmtpError.PARSE_ERROR("Reply code wrong length: %s (%d)", str, str.length);
40
41        int as_int = int.parse(str);
42        if (as_int < MIN || as_int > MAX)
43            throw new SmtpError.PARSE_ERROR("Reply code out of range: %s", str);
44
45        this.str = str;
46    }
47
48    public Status get_status() {
49        int i = Ascii.digit_to_int(str[0]);
50
51        // This works because of the checks in the constructor; Condition can't be checked so
52        // easily
53        return (i != -1) ? (Status) i : Status.UNKNOWN;
54    }
55
56    public Condition get_condition() {
57        switch (Ascii.digit_to_int(str[1])) {
58            case Condition.SYNTAX:
59                return Condition.SYNTAX;
60
61            case Condition.ADDITIONAL_INFO:
62                return Condition.ADDITIONAL_INFO;
63
64            case Condition.COMM_CHANNEL:
65                return Condition.COMM_CHANNEL;
66
67            case Condition.MAIL_SYSTEM:
68                return Condition.MAIL_SYSTEM;
69
70            default:
71                return Condition.UNKNOWN;
72        }
73    }
74
75    public bool is_success_completed() {
76        return get_status() == Status.POSITIVE_COMPLETION;
77    }
78
79    public bool is_success_intermediate() {
80        switch (get_status()) {
81            case Status.POSITIVE_PRELIMINARY:
82            case Status.POSITIVE_INTERMEDIATE:
83                return true;
84
85            default:
86                return false;
87        }
88    }
89
90    public bool is_failure() {
91        switch (get_status()) {
92            case Status.PERMANENT_FAILURE:
93            case Status.TRANSIENT_NEGATIVE:
94                return true;
95
96            default:
97                return false;
98        }
99    }
100
101    public bool is_start_data() {
102        return str == START_DATA_CODE;
103    }
104
105    public bool is_starttls_ready() {
106        return str == STARTTLS_READY_CODE;
107    }
108
109    public bool is_denied() {
110        return str == DENIED_CODE;
111    }
112
113    /**
114     * Returns true for [@link Status.PERMANENT_FAILURE} {@link Condition.SYNTAX} errors.
115     *
116     * Generally this means the command (or sequence of commands) was unknown or unimplemented,
117     * i.e. "500 Syntax error", "502 Command not implemented", etc.
118     *
119     * See [[http://tools.ietf.org/html/rfc5321#section-4.2.2]]
120     */
121    public bool is_syntax_error() {
122        return get_status() == ResponseCode.Status.PERMANENT_FAILURE
123            && get_condition() == ResponseCode.Condition.SYNTAX;
124    }
125
126    public string serialize() {
127        return str;
128    }
129
130    public string to_string() {
131        return str;
132    }
133}
134
135