// // Annotationentry.cs // // Author: Jeffrey Stedfast // // Copyright (c) 2013-2020 .NET Foundation and Contributors // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. // using System; namespace MailKit { /// /// An annotation entry. /// /// /// An annotation entry. /// For more information about annotations, see /// rfc5257. /// public class AnnotationEntry : IEquatable { /// /// An annotation entry for a comment on a message. /// /// /// Used to get or set a comment on a message. /// public static readonly AnnotationEntry Comment = new AnnotationEntry ("/comment", AnnotationScope.Both); /// /// An annotation entry for a private comment on a message. /// /// /// Used to get or set a private comment on a message. /// public static readonly AnnotationEntry PrivateComment = new AnnotationEntry ("/comment", AnnotationScope.Private); /// /// An annotation entry for a shared comment on a message. /// /// /// Used to get or set a shared comment on a message. /// public static readonly AnnotationEntry SharedComment = new AnnotationEntry ("/comment", AnnotationScope.Shared); /// /// An annotation entry for flags on a message. /// /// /// Used to get or set flags on a message. /// public static readonly AnnotationEntry Flags = new AnnotationEntry ("/flags", AnnotationScope.Both); /// /// An annotation entry for private flags on a message. /// /// /// Used to get or set private flags on a message. /// public static readonly AnnotationEntry PrivateFlags = new AnnotationEntry ("/flags", AnnotationScope.Private); /// /// Aa annotation entry for shared flags on a message. /// /// /// Used to get or set shared flags on a message. /// public static readonly AnnotationEntry SharedFlags = new AnnotationEntry ("/flags", AnnotationScope.Shared); /// /// An annotation entry for an alternate subject on a message. /// /// /// Used to get or set an alternate subject on a message. /// public static readonly AnnotationEntry AltSubject = new AnnotationEntry ("/altsubject", AnnotationScope.Both); /// /// An annotation entry for a private alternate subject on a message. /// /// /// Used to get or set a private alternate subject on a message. /// public static readonly AnnotationEntry PrivateAltSubject = new AnnotationEntry ("/altsubject", AnnotationScope.Private); /// /// An annotation entry for a shared alternate subject on a message. /// /// /// Used to get or set a shared alternate subject on a message. /// public static readonly AnnotationEntry SharedAltSubject = new AnnotationEntry ("/altsubject", AnnotationScope.Shared); static void ValidatePath (string path) { if (path == null) throw new ArgumentNullException (nameof (path)); if (path.Length == 0) throw new ArgumentException ("Annotation entry paths cannot be empty.", nameof (path)); if (path[0] != '/' && path[0] != '*' && path[0] != '%') throw new ArgumentException ("Annotation entry paths must begin with '/'.", nameof (path)); if (path.Length > 1 && path[1] >= '0' && path[1] <= '9') throw new ArgumentException ("Annotation entry paths must not include a part-specifier.", nameof (path)); if (path == "*" || path == "%") return; char pc = path[0]; for (int i = 1; i < path.Length; i++) { char c = path[i]; if (c > 127) throw new ArgumentException ($"Invalid character in annotation entry path: '{c}'.", nameof (path)); if (c >= '0' && c <= '9' && pc == '/') throw new ArgumentException ("Invalid annotation entry path.", nameof (path)); if ((pc == '/' || pc == '.') && (c == '/' || c == '.')) throw new ArgumentException ("Invalid annotation entry path.", nameof (path)); pc = c; } int endIndex = path.Length - 1; if (path[endIndex] == '/') throw new ArgumentException ("Annotation entry paths must not end with '/'.", nameof (path)); if (path[endIndex] == '.') throw new ArgumentException ("Annotation entry paths must not end with '.'.", nameof (path)); } static void ValidatePartSpecifier (string partSpecifier) { if (partSpecifier == null) throw new ArgumentNullException (nameof (partSpecifier)); char pc = '\0'; for (int i = 0; i < partSpecifier.Length; i++) { char c = partSpecifier[i]; if (!((c >= '0' && c <= '9') || c == '.') || (c == '.' && (pc == '.' || pc == '\0'))) throw new ArgumentException ("Invalid part-specifier.", nameof (partSpecifier)); pc = c; } if (pc == '.') throw new ArgumentException ("Invalid part-specifier.", nameof (partSpecifier)); } AnnotationEntry () { } /// /// Initializes a new instance of the struct. /// /// /// Creates a new . /// /// The annotation entry path. /// The scope of the annotation. /// /// is null. /// /// /// is invalid. /// public AnnotationEntry (string path, AnnotationScope scope = AnnotationScope.Both) { ValidatePath (path); switch (scope) { case AnnotationScope.Private: Entry = path + ".priv"; break; case AnnotationScope.Shared: Entry = path + ".shared"; break; default: Entry = path; break; } PartSpecifier = null; Path = path; Scope = scope; } /// /// Initializes a new instance of the struct. /// /// /// Creates a new for an individual body part of a message. /// /// The part-specifier of the body part of the message. /// The annotation entry path. /// The scope of the annotation. /// /// is null. /// -or- /// is null. /// /// /// is invalid. /// -or- /// is invalid. /// public AnnotationEntry (string partSpecifier, string path, AnnotationScope scope = AnnotationScope.Both) { ValidatePartSpecifier (partSpecifier); ValidatePath (path); switch (scope) { case AnnotationScope.Private: Entry = string.Format ("/{0}{1}.priv", partSpecifier, path); break; case AnnotationScope.Shared: Entry = string.Format ("/{0}{1}.shared", partSpecifier, path); break; default: Entry = string.Format ("/{0}{1}", partSpecifier, path); break; } PartSpecifier = partSpecifier; Path = path; Scope = scope; } /// /// Initializes a new instance of the struct. /// /// /// Creates a new for an individual body part of a message. /// /// The body part of the message. /// The annotation entry path. /// The scope of the annotation. /// /// is null. /// -or- /// is null. /// /// /// is invalid. /// public AnnotationEntry (BodyPart part, string path, AnnotationScope scope = AnnotationScope.Both) { if (part == null) throw new ArgumentNullException (nameof (part)); ValidatePath (path); switch (scope) { case AnnotationScope.Private: Entry = string.Format ("/{0}{1}.priv", part.PartSpecifier, path); break; case AnnotationScope.Shared: Entry = string.Format ("/{0}{1}.shared", part.PartSpecifier, path); break; default: Entry = string.Format ("/{0}{1}", part.PartSpecifier, path); break; } PartSpecifier = part.PartSpecifier; Path = path; Scope = scope; } /// /// Get the annotation entry specifier. /// /// /// Gets the annotation entry specifier. /// /// The annotation entry specifier. public string Entry { get; private set; } /// /// Get the part-specifier component of the annotation entry. /// /// /// Gets the part-specifier component of the annotation entry. /// public string PartSpecifier { get; private set; } /// /// Get the path component of the annotation entry. /// /// /// Gets the path component of the annotation entry. /// public string Path { get; private set; } /// /// Get the scope of the annotation. /// /// /// Gets the scope of the annotation. /// public AnnotationScope Scope { get; private set; } #region IEquatable implementation /// /// Determines whether the specified is equal to the current . /// /// /// Determines whether the specified is equal to the current . /// /// The to compare with the current . /// true if the specified is equal to the current /// ; otherwise, false. public bool Equals (AnnotationEntry other) { return other?.Entry == Entry; } #endregion /// /// Determines whether two annotation entries are equal. /// /// /// Determines whether two annotation entries are equal. /// /// true if and are equal; otherwise, false. /// The first annotation entry to compare. /// The second annotation entry to compare. public static bool operator == (AnnotationEntry entry1, AnnotationEntry entry2) { return entry1?.Entry == entry2?.Entry; } /// /// Determines whether two annotation entries are not equal. /// /// /// Determines whether two annotation entries are not equal. /// /// true if and are not equal; otherwise, false. /// The first annotation entry to compare. /// The second annotation entry to compare. public static bool operator != (AnnotationEntry entry1, AnnotationEntry entry2) { return entry1?.Entry != entry2?.Entry; } /// /// Determine whether the specified is equal to the current . /// /// /// Determines whether the specified is equal to the current . /// /// The to compare with the current . /// true if the specified is equal to the current /// ; otherwise, false. public override bool Equals (object obj) { return obj is AnnotationEntry && ((AnnotationEntry) obj).Entry == Entry; } /// /// Serves as a hash function for a object. /// /// /// Serves as a hash function for a object. /// /// A hash code for this instance that is suitable for use in hashing algorithms and data structures such as a hash table. public override int GetHashCode () { return Entry.GetHashCode (); } /// /// Returns a that represents the current . /// /// /// Returns a that represents the current . /// /// A that represents the current . public override string ToString () { return Entry; } /// /// Parse an annotation entry. /// /// /// Parses an annotation entry. /// /// The annotation entry. /// The parsed annotation entry. /// /// is null. /// /// /// does not conform to the annotation entry syntax. /// public static AnnotationEntry Parse (string entry) { if (entry == null) throw new ArgumentNullException (nameof (entry)); if (entry.Length == 0) throw new FormatException ("An annotation entry cannot be empty."); if (entry[0] != '/' && entry[0] != '*' && entry[0] != '%') throw new FormatException ("An annotation entry must begin with a '/' character."); var scope = AnnotationScope.Both; int startIndex = 0, endIndex; string partSpecifier = null; var component = 0; var pc = entry[0]; string path; for (int i = 1; i < entry.Length; i++) { char c = entry[i]; if (c >= '0' && c <= '9' && pc == '/') { if (component > 0) throw new FormatException ("Invalid annotation entry."); startIndex = i; endIndex = i + 1; pc = c; while (endIndex < entry.Length) { c = entry[endIndex]; if (c == '/') { if (pc == '.') throw new FormatException ("Invalid part-specifier in annotation entry."); break; } if (!(c >= '0' && c <= '9') && c != '.') throw new FormatException ($"Invalid character in part-specifier: '{c}'."); if (c == '.' && pc == '.') throw new FormatException ("Invalid part-specifier in annotation entry."); endIndex++; pc = c; } if (endIndex >= entry.Length) throw new FormatException ("Incomplete part-specifier in annotation entry."); partSpecifier = entry.Substring (startIndex, endIndex - startIndex); i = startIndex = endIndex; component++; } else if (c == '/' || c == '.') { if (pc == '/' || pc == '.') throw new FormatException ("Invalid annotation entry path."); if (c == '/') component++; } else if (c > 127) { throw new FormatException ($"Invalid character in annotation entry path: '{c}'."); } pc = c; } if (pc == '/' || pc == '.') throw new FormatException ("Invalid annotation entry path."); if (entry.EndsWith (".shared", StringComparison.Ordinal)) { endIndex = entry.Length - ".shared".Length; scope = AnnotationScope.Shared; } else if (entry.EndsWith (".priv", StringComparison.Ordinal)) { endIndex = entry.Length - ".priv".Length; scope = AnnotationScope.Private; } else { endIndex = entry.Length; } path = entry.Substring (startIndex, endIndex - startIndex); return new AnnotationEntry { PartSpecifier = partSpecifier, Entry = entry, Path = path, Scope = scope }; } internal static AnnotationEntry Create (string entry) { switch (entry) { case "/comment": return Comment; case "/comment.priv": return PrivateComment; case "/comment.shared": return SharedComment; case "/flags": return Flags; case "/flags.priv": return PrivateFlags; case "/flags.shared": return SharedFlags; case "/altsubject": return AltSubject; case "/altsubject.priv": return PrivateAltSubject; case "/altsubject.shared": return SharedAltSubject; default: return Parse (entry); } } } }