| File: System\Net\Mime\ContentType.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.Mail\src\System.Net.Mail.csproj (System.Net.Mail) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Net.Mail; using System.Text; namespace System.Net.Mime { // Typed Content-Type header // // We parse the type during construction and set. // null and string.empty will throw for construction,set and mediatype/subtype // constructors set isPersisted to false. isPersisted needs to be tracked separately // than isChanged because isChanged only determines if the cached value should be used. // isPersisted tracks if the object has been persisted. However, obviously if isChanged is true // the object isn't persisted. // If any subcomponents are changed, isChanged is set to true and isPersisted is false // ToString caches the value until a isChanged is true, then it recomputes the full value. public class ContentType { private readonly TrackingStringDictionary _parameters = new TrackingStringDictionary(); private string _mediaType; private string _subType; private bool _isChanged; private string _type; private bool _isPersisted; /// <summary> /// Default content type - can be used if the Content-Type header /// is not defined in the message headers. /// </summary> internal const string Default = "application/octet-stream"; public ContentType() : this(Default) { } /// <summary> /// ctor. /// </summary> /// <param name="contentType">Unparsed value of the Content-Type header.</param> public ContentType(string contentType) { ArgumentException.ThrowIfNullOrEmpty(contentType); _isChanged = true; _type = contentType; ParseValue(); } public string? Boundary { get { return Parameters["boundary"]; } set { if (string.IsNullOrEmpty(value)) { Parameters.Remove("boundary"); } else { Parameters["boundary"] = value; } } } public string? CharSet { get { return Parameters["charset"]; } set { if (string.IsNullOrEmpty(value)) { Parameters.Remove("charset"); } else { Parameters["charset"] = value; } } } /// <summary> /// Gets the media type. /// </summary> public string MediaType { get { return _mediaType + "/" + _subType; } set { ArgumentException.ThrowIfNullOrEmpty(value); int offset = 0; _mediaType = MailBnfHelper.ReadToken(value, ref offset); if (_mediaType.Length == 0 || offset >= value.Length || value[offset++] != '/') throw new FormatException(SR.MediaTypeInvalid); _subType = MailBnfHelper.ReadToken(value, ref offset); if (_subType.Length == 0 || offset < value.Length) { throw new FormatException(SR.MediaTypeInvalid); } _isChanged = true; _isPersisted = false; } } [AllowNull] public string Name { get { string? value = Parameters["name"]; Encoding? nameEncoding = MimeBasePart.DecodeEncoding(value); if (nameEncoding != null) { value = MimeBasePart.DecodeHeaderValue(value); } return value!; } set { if (value == null || value == string.Empty) { Parameters.Remove("name"); } else { Parameters["name"] = value; } } } public StringDictionary Parameters => _parameters; internal void Set(string contentType, HeaderCollection headers) { _type = contentType; ParseValue(); headers.InternalSet(MailHeaderInfo.GetString(MailHeaderID.ContentType)!, ToString()); _isPersisted = true; } internal void PersistIfNeeded(HeaderCollection headers, bool forcePersist) { if (IsChanged || !_isPersisted || forcePersist) { headers.InternalSet(MailHeaderInfo.GetString(MailHeaderID.ContentType)!, ToString()); _isPersisted = true; } } internal bool IsChanged => _isChanged || _parameters != null && _parameters.IsChanged; public override string ToString() { if (_type == null || IsChanged) { _type = Encode(false); // Legacy wire-safe format _isChanged = false; _parameters.IsChanged = false; _isPersisted = false; } return _type; } internal string Encode(bool allowUnicode) { var builder = new StringBuilder(); builder.Append(_mediaType); // Must not have unicode, already validated builder.Append('/'); builder.Append(_subType); // Must not have unicode, already validated // Validate and encode unicode where required foreach (string key in Parameters.Keys) { builder.Append("; "); EncodeToBuffer(key, builder, allowUnicode); builder.Append('='); EncodeToBuffer(_parameters[key]!, builder, allowUnicode); } return builder.ToString(); } private static void EncodeToBuffer(string value, StringBuilder builder, bool allowUnicode) { Encoding? encoding = MimeBasePart.DecodeEncoding(value); if (encoding != null) // Manually encoded elsewhere, pass through { builder.Append('\"').Append(value).Append('"'); } else if ((allowUnicode && !MailBnfHelper.HasCROrLF(value)) // Unicode without CL or LF's || MimeBasePart.IsAscii(value, false)) // Ascii { MailBnfHelper.GetTokenOrQuotedString(value, builder, allowUnicode); } else { // MIME Encoding required encoding = Encoding.GetEncoding(MimeBasePart.DefaultCharSet); builder.Append('"').Append(MimeBasePart.EncodeHeaderValue(value, encoding, MimeBasePart.ShouldUseBase64Encoding(encoding))).Append('"'); } } public override bool Equals([NotNullWhen(true)] object? rparam) => rparam == null ? false : string.Equals(ToString(), rparam.ToString(), StringComparison.OrdinalIgnoreCase); public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(ToString()); // Helper methods. [MemberNotNull(nameof(_mediaType))] [MemberNotNull(nameof(_subType))] private void ParseValue() { try { int offset = 0; _mediaType = MailBnfHelper.ReadToken(_type, ref offset); if (string.IsNullOrEmpty(_mediaType) || offset >= _type.Length || _type[offset++] != '/') { throw new FormatException(SR.ContentTypeInvalid); } _subType = MailBnfHelper.ReadToken(_type, ref offset); if (string.IsNullOrEmpty(_subType)) { throw new FormatException(SR.ContentTypeInvalid); } while (MailBnfHelper.SkipCFWS(_type, ref offset)) { if (_type[offset++] != ';') { throw new FormatException(SR.ContentTypeInvalid); } if (!MailBnfHelper.SkipCFWS(_type, ref offset)) { break; } string? paramAttribute = MailBnfHelper.ReadParameterAttribute(_type, ref offset); if (string.IsNullOrEmpty(paramAttribute)) { throw new FormatException(SR.ContentTypeInvalid); } string? paramValue; if (offset >= _type.Length || _type[offset++] != '=') { throw new FormatException(SR.ContentTypeInvalid); } if (!MailBnfHelper.SkipCFWS(_type, ref offset)) { throw new FormatException(SR.ContentTypeInvalid); } paramValue = _type[offset] == '"' ? MailBnfHelper.ReadQuotedString(_type, ref offset, null) : MailBnfHelper.ReadToken(_type, ref offset); if (paramValue == null) { throw new FormatException(SR.ContentTypeInvalid); } _parameters.Add(paramAttribute, paramValue); } _parameters.IsChanged = false; } catch (FormatException fe) when (fe.Message != SR.ContentTypeInvalid) { throw new FormatException(SR.ContentTypeInvalid); } } } }