using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Text; namespace Swan.Net.Smtp { /// /// Represents an SMTP server response object. /// public class SmtpServerReply { #region Constructors /// /// Initializes a new instance of the class. /// /// The response code. /// The status code. /// The content. public SmtpServerReply(Int32 responseCode, String statusCode, params String[] content) { this.Content = new List(); this.ReplyCode = responseCode; this.EnhancedStatusCode = statusCode; this.Content.AddRange(content); this.IsValid = responseCode >= 200 && responseCode < 600; this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown; this.ReplyCodeCategory = SmtpReplyCodeCategories.Unknown; if(!this.IsValid) { return; } if(responseCode >= 200) { this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveCompletion; } if(responseCode >= 300) { this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PositiveIntermediate; } if(responseCode >= 400) { this.ReplyCodeSeverity = SmtpReplyCodeSeverities.TransientNegative; } if(responseCode >= 500) { this.ReplyCodeSeverity = SmtpReplyCodeSeverities.PermanentNegative; } if(responseCode >= 600) { this.ReplyCodeSeverity = SmtpReplyCodeSeverities.Unknown; } if(Int32.TryParse(responseCode.ToString(CultureInfo.InvariantCulture).Substring(1, 1), out Int32 middleDigit)) { if(middleDigit >= 0 && middleDigit <= 5) { this.ReplyCodeCategory = (SmtpReplyCodeCategories)middleDigit; } } } /// /// Initializes a new instance of the class. /// public SmtpServerReply() : this(0, String.Empty, String.Empty) { // placeholder } /// /// Initializes a new instance of the class. /// /// The response code. /// The status code. /// The content. public SmtpServerReply(Int32 responseCode, String statusCode, String content) : this(responseCode, statusCode, new[] { content }) { } /// /// Initializes a new instance of the class. /// /// The response code. /// The content. public SmtpServerReply(Int32 responseCode, String content) : this(responseCode, String.Empty, content) { } #endregion #region Pre-built responses (https://tools.ietf.org/html/rfc5321#section-4.2.2) /// /// Gets the command unrecognized reply. /// public static SmtpServerReply CommandUnrecognized => new SmtpServerReply(500, "Syntax error, command unrecognized"); /// /// Gets the syntax error arguments reply. /// public static SmtpServerReply SyntaxErrorArguments => new SmtpServerReply(501, "Syntax error in parameters or arguments"); /// /// Gets the command not implemented reply. /// public static SmtpServerReply CommandNotImplemented => new SmtpServerReply(502, "Command not implemented"); /// /// Gets the bad sequence of commands reply. /// public static SmtpServerReply BadSequenceOfCommands => new SmtpServerReply(503, "Bad sequence of commands"); /// /// Gets the protocol violation reply. /// = public static SmtpServerReply ProtocolViolation => new SmtpServerReply(451, "Requested action aborted: error in processing"); /// /// Gets the system status bye reply. /// public static SmtpServerReply SystemStatusBye => new SmtpServerReply(221, "Service closing transmission channel"); /// /// Gets the system status help reply. /// = public static SmtpServerReply SystemStatusHelp => new SmtpServerReply(221, "Refer to RFC 5321"); /// /// Gets the bad syntax command empty reply. /// public static SmtpServerReply BadSyntaxCommandEmpty => new SmtpServerReply(400, "Error: bad syntax"); /// /// Gets the OK reply. /// public static SmtpServerReply Ok => new SmtpServerReply(250, "OK"); /// /// Gets the authorization required reply. /// public static SmtpServerReply AuthorizationRequired => new SmtpServerReply(530, "Authorization Required"); #endregion #region Properties /// /// Gets the response severity. /// public SmtpReplyCodeSeverities ReplyCodeSeverity { get; } /// /// Gets the response category. /// public SmtpReplyCodeCategories ReplyCodeCategory { get; } /// /// Gets the numeric response code. /// public Int32 ReplyCode { get; } /// /// Gets the enhanced status code. /// public String EnhancedStatusCode { get; } /// /// Gets the content. /// public List Content { get; } /// /// Returns true if the response code is between 200 and 599. /// public Boolean IsValid { get; } /// /// Gets a value indicating whether this instance is positive. /// public Boolean IsPositive => this.ReplyCode >= 200 && this.ReplyCode <= 399; #endregion #region Methods /// /// Parses the specified text into a Server Reply for thorough analysis. /// /// The text. /// A new instance of SMTP server response object. public static SmtpServerReply Parse(String text) { String[] lines = text.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries); if(lines.Length == 0) { return new SmtpServerReply(); } String[] lastLineParts = lines.Last().Split(new[] { " " }, StringSplitOptions.RemoveEmptyEntries); String enhancedStatusCode = String.Empty; _ = Int32.TryParse(lastLineParts[0], out Int32 responseCode); if(lastLineParts.Length > 1) { if(lastLineParts[1].Split('.').Length == 3) { enhancedStatusCode = lastLineParts[1]; } } List content = new List(); for(Int32 i = 0; i < lines.Length; i++) { String splitChar = i == lines.Length - 1 ? " " : "-"; String[] lineParts = lines[i].Split(new[] { splitChar }, 2, StringSplitOptions.None); String lineContent = lineParts.Last(); if(String.IsNullOrWhiteSpace(enhancedStatusCode) == false) { lineContent = lineContent.Replace(enhancedStatusCode, String.Empty).Trim(); } content.Add(lineContent); } return new SmtpServerReply(responseCode, enhancedStatusCode, content.ToArray()); } /// /// Returns a that represents this instance. /// /// /// A that represents this instance. /// public override String ToString() { String responseCodeText = this.ReplyCode.ToString(CultureInfo.InvariantCulture); String statusCodeText = String.IsNullOrWhiteSpace(this.EnhancedStatusCode) ? String.Empty : $" {this.EnhancedStatusCode.Trim()}"; if(this.Content.Count == 0) { return $"{responseCodeText}{statusCodeText}"; } StringBuilder builder = new StringBuilder(); for(Int32 i = 0; i < this.Content.Count; i++) { Boolean isLastLine = i == this.Content.Count - 1; _ = builder.Append(isLastLine ? $"{responseCodeText}{statusCodeText} {this.Content[i]}" : $"{responseCodeText}-{this.Content[i]}\r\n"); } return builder.ToString(); } #endregion } }