| File: System\Net\Mime\ContentDisposition.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.Generic; using System.Collections.Specialized; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Net.Mail; using System.Text; namespace System.Net.Mime { public class ContentDisposition { private const string CreationDateKey = "creation-date"; private const string ModificationDateKey = "modification-date"; private const string ReadDateKey = "read-date"; private const string FileNameKey = "filename"; private const string SizeKey = "size"; private TrackingValidationObjectDictionary? _parameters; private string _disposition; private string _dispositionType; private bool _isChanged; private bool _isPersisted; private static readonly TrackingValidationObjectDictionary.ValidateAndParseValue s_dateParser = new TrackingValidationObjectDictionary.ValidateAndParseValue(v => new SmtpDateTime(v.ToString()!)); // this will throw a FormatException if the value supplied is not a valid SmtpDateTime private static readonly TrackingValidationObjectDictionary.ValidateAndParseValue s_longParser = new TrackingValidationObjectDictionary.ValidateAndParseValue((object value) => { long longValue; if (!long.TryParse(value.ToString(), NumberStyles.None, CultureInfo.InvariantCulture, out longValue)) { throw new FormatException(SR.ContentDispositionInvalid); } return longValue; }); private static readonly Dictionary<string, TrackingValidationObjectDictionary.ValidateAndParseValue> s_validators = new Dictionary<string, TrackingValidationObjectDictionary.ValidateAndParseValue>() { { CreationDateKey, s_dateParser }, { ModificationDateKey, s_dateParser }, { ReadDateKey, s_dateParser }, { SizeKey, s_longParser } }; public ContentDisposition() { _isChanged = true; _disposition = _dispositionType = "attachment"; // no need to parse disposition since there's nothing to parse } public ContentDisposition(string disposition) { ArgumentNullException.ThrowIfNull(disposition); _isChanged = true; _disposition = disposition; ParseValue(); } internal DateTime GetDateParameter(string parameterName) { SmtpDateTime? dateValue = ((TrackingValidationObjectDictionary)Parameters).InternalGet(parameterName) as SmtpDateTime; return dateValue == null ? DateTime.MinValue : dateValue.Date; } /// <summary> /// Gets the disposition type of the content. /// </summary> public string DispositionType { get { return _dispositionType; } set { ArgumentException.ThrowIfNullOrEmpty(value); _isChanged = true; _dispositionType = value; } } public StringDictionary Parameters => _parameters ??= new TrackingValidationObjectDictionary(s_validators); /// <summary> /// Gets the value of the Filename parameter. /// </summary> public string? FileName { get { return Parameters[FileNameKey]; } set { if (string.IsNullOrEmpty(value)) { Parameters.Remove(FileNameKey); } else { Parameters[FileNameKey] = value; } } } /// <summary> /// Gets the value of the Creation-Date parameter. /// </summary> public DateTime CreationDate { get { return GetDateParameter(CreationDateKey); } set { SmtpDateTime date = new SmtpDateTime(value); ((TrackingValidationObjectDictionary)Parameters).InternalSet(CreationDateKey, date); } } /// <summary> /// Gets the value of the Modification-Date parameter. /// </summary> public DateTime ModificationDate { get { return GetDateParameter(ModificationDateKey); } set { SmtpDateTime date = new SmtpDateTime(value); ((TrackingValidationObjectDictionary)Parameters).InternalSet(ModificationDateKey, date); } } public bool Inline { get { return _dispositionType == DispositionTypeNames.Inline; } set { _isChanged = true; _dispositionType = value ? DispositionTypeNames.Inline : DispositionTypeNames.Attachment; } } /// <summary> /// Gets the value of the Read-Date parameter. /// </summary> public DateTime ReadDate { get { return GetDateParameter(ReadDateKey); } set { SmtpDateTime date = new SmtpDateTime(value); ((TrackingValidationObjectDictionary)Parameters).InternalSet(ReadDateKey, date); } } /// <summary> /// Gets the value of the Size parameter (-1 if unspecified). /// </summary> public long Size { get { object? sizeValue = ((TrackingValidationObjectDictionary)Parameters).InternalGet(SizeKey); return sizeValue == null ? -1 : (long)sizeValue; } set { ((TrackingValidationObjectDictionary)Parameters).InternalSet(SizeKey, value); } } internal void Set(string contentDisposition, HeaderCollection headers) { // we don't set ischanged because persistence was already handled // via the headers. _disposition = contentDisposition; ParseValue(); headers.InternalSet(MailHeaderInfo.GetString(MailHeaderID.ContentDisposition)!, ToString()); _isPersisted = true; } internal void PersistIfNeeded(HeaderCollection headers, bool forcePersist) { if (IsChanged || !_isPersisted || forcePersist) { headers.InternalSet(MailHeaderInfo.GetString(MailHeaderID.ContentDisposition)!, ToString()); _isPersisted = true; } } internal bool IsChanged => _isChanged || _parameters != null && _parameters.IsChanged; public override string ToString() { if (_disposition == null || _isChanged || _parameters != null && _parameters.IsChanged) { _disposition = Encode(false); // Legacy wire-safe format _isChanged = false; _parameters!.IsChanged = false; _isPersisted = false; } return _disposition; } internal string Encode(bool allowUnicode) { var builder = new StringBuilder(); builder.Append(_dispositionType); // 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) { return rparam == null ? false : string.Equals(ToString(), rparam.ToString(), StringComparison.OrdinalIgnoreCase); } public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(ToString()); [MemberNotNull(nameof(_dispositionType))] private void ParseValue() { int offset = 0; try { // the disposition MUST be the first parameter in the string _dispositionType = MailBnfHelper.ReadToken(_disposition, ref offset); // disposition MUST not be empty if (string.IsNullOrEmpty(_dispositionType)) { throw new FormatException(SR.MailHeaderFieldMalformedHeader); } // now we know that there are parameters so we must initialize or clear // and parse if (_parameters == null) { _parameters = new TrackingValidationObjectDictionary(s_validators); } else { _parameters.Clear(); } while (MailBnfHelper.SkipCFWS(_disposition, ref offset)) { // ensure that the separator charactor is present if (_disposition[offset++] != ';') { throw new FormatException(SR.Format(SR.MailHeaderFieldInvalidCharacter, _disposition[offset - 1])); } // skip whitespace and see if there's anything left to parse or if we're done if (!MailBnfHelper.SkipCFWS(_disposition, ref offset)) { break; } string? paramAttribute = MailBnfHelper.ReadParameterAttribute(_disposition, ref offset); string? paramValue; // verify the next character after the parameter is correct if (_disposition[offset++] != '=') { throw new FormatException(SR.MailHeaderFieldMalformedHeader); } if (!MailBnfHelper.SkipCFWS(_disposition, ref offset)) { // parameter was at end of string and has no value // this is not valid throw new FormatException(SR.ContentDispositionInvalid); } paramValue = _disposition[offset] == '"' ? MailBnfHelper.ReadQuotedString(_disposition, ref offset, null) : MailBnfHelper.ReadToken(_disposition, ref offset); // paramValue could potentially still be empty if it was a valid quoted string that // contained no inner value. this is invalid if (string.IsNullOrEmpty(paramAttribute) || string.IsNullOrEmpty(paramValue)) { throw new FormatException(SR.ContentDispositionInvalid); } // if validation is needed, the parameters dictionary will have a validator registered // for the parameter that is being set so no additional formatting checks are needed here Parameters.Add(paramAttribute, paramValue); } } catch (FormatException exception) { // it's possible that something in MailBNFHelper could throw so ensure that we catch it and wrap it // so that the exception has the correct text throw new FormatException(SR.ContentDispositionInvalid, exception); } _parameters.IsChanged = false; } } }