File: Microsoft.NetFramework.Analyzers\MarkVerbHandlersWithValidateAntiforgeryTokenAnalyzer.MvcAttributeSymbols.cs
Web Access
Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;

namespace Microsoft.NetFramework.Analyzers
{
    public partial class MarkVerbHandlersWithValidateAntiforgeryTokenAnalyzer
    {
        /// <summary>
        /// Helper for examining System.Web.Mvc attributes on MVC controller methods.
        /// </summary>
        private sealed class MvcAttributeSymbols
        {
            private INamedTypeSymbol? ValidateAntiforgeryTokenAttributeSymbol { get; set; }
            private INamedTypeSymbol? HttpGetAttributeSymbol { get; set; }
            private INamedTypeSymbol? HttpPostAttributeSymbol { get; set; }
            private INamedTypeSymbol? HttpPutAttributeSymbol { get; set; }
            private INamedTypeSymbol? HttpDeleteAttributeSymbol { get; set; }
            private INamedTypeSymbol? HttpPatchAttributeSymbol { get; set; }
            private INamedTypeSymbol? AcceptVerbsAttributeSymbol { get; set; }
            private INamedTypeSymbol? NonActionAttributeSymbol { get; set; }
            private INamedTypeSymbol? ChildActionOnlyAttributeSymbol { get; set; }
            private INamedTypeSymbol? HttpVerbsSymbol { get; set; }

            public MvcAttributeSymbols(Compilation compilation)
            {
                this.ValidateAntiforgeryTokenAttributeSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcValidateAntiForgeryTokenAttribute);
                this.HttpGetAttributeSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcHttpGetAttribute);
                this.HttpPostAttributeSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcHttpPostAttribute);
                this.HttpPutAttributeSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcHttpPutAttribute);
                this.HttpDeleteAttributeSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcHttpDeleteAttribute);
                this.HttpPatchAttributeSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcHttpPatchAttribute);
                this.AcceptVerbsAttributeSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcAcceptVerbsAttribute);
                this.NonActionAttributeSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcNonActionAttribute);
                this.ChildActionOnlyAttributeSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcChildActionOnlyAttribute);
                this.HttpVerbsSymbol = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemWebMvcHttpVerbs);
            }

            /// <summary>
            /// Gets relevant info from the attributes on the MVC controller action we're examining.
            /// </summary>
            /// <param name="attributeDatas">Attributes on the MVC controller action.</param>
            /// <param name="verbs">Information on which HTTP verbs are specified.</param>
            /// <param name="antiforgeryTokenDefined">Indicates that the ValidateAntiforgeryToken attribute was specified.</param>
            /// <param name="isAction">Indicates that the MVC controller method doesn't have an attribute saying it's not really an action.</param>
            public void ComputeAttributeInfo(
                ImmutableArray<AttributeData> attributeDatas,
                out MvcHttpVerbs verbs,
                out bool antiforgeryTokenDefined,
                out bool isAction)
            {
                verbs = default;
                antiforgeryTokenDefined = false;
                isAction = true;    // Presumed an MVC controller action until proven otherwise.

                foreach (AttributeData a in attributeDatas)
                {
                    if (IsAttributeClass(a, this.ValidateAntiforgeryTokenAttributeSymbol))
                    {
                        antiforgeryTokenDefined = true;
                    }
                    else if (IsAttributeClass(a, this.HttpGetAttributeSymbol))
                    {
                        verbs |= MvcHttpVerbs.Get;
                    }
                    else if (IsAttributeClass(a, this.HttpPostAttributeSymbol))
                    {
                        verbs |= MvcHttpVerbs.Post;
                    }
                    else if (IsAttributeClass(a, this.HttpPutAttributeSymbol))
                    {
                        verbs |= MvcHttpVerbs.Put;
                    }
                    else if (IsAttributeClass(a, this.HttpDeleteAttributeSymbol))
                    {
                        verbs |= MvcHttpVerbs.Delete;
                    }
                    else if (IsAttributeClass(a, this.HttpPatchAttributeSymbol))
                    {
                        verbs |= MvcHttpVerbs.Patch;
                    }
                    else if (IsAttributeClass(a, this.AcceptVerbsAttributeSymbol))
                    {
                        if (a.AttributeConstructor?.Parameters.Length == 1)
                        {
                            ITypeSymbol parameterType = a.AttributeConstructor.Parameters[0].Type;
                            if (a.AttributeConstructor.Parameters[0].IsParams
                                && parameterType.TypeKind == TypeKind.Array)
                            {
                                IArrayTypeSymbol parameterArrayType = (IArrayTypeSymbol)parameterType;
                                if (parameterArrayType.Rank == 1
                                    && parameterArrayType.ElementType.SpecialType == SpecialType.System_String)
                                {
                                    // The [AcceptVerbs("Put", "Post")] case.

                                    foreach (TypedConstant tc in a.ConstructorArguments[0].Values)
                                    {
                                        if (tc.Value is string s && Enum.TryParse(s, true /* ignoreCase */, out MvcHttpVerbs v))
                                        {
                                            verbs |= v;
                                        }
                                    }

                                    continue;
                                }
                            }
                            else if (parameterType.TypeKind == TypeKind.Enum
                                && Equals(parameterType, this.HttpVerbsSymbol))
                            {
                                // The [AcceptVerbs(HttpVerbs.Delete)] case.

                                int i = (int)a.ConstructorArguments[0].Value!;
                                verbs |= (MvcHttpVerbs)i;

                                continue;
                            }
                        }

                        // If we reach here, then we didn't handle the [AcceptVerbs] constructor overload.
                    }
                    else if (IsAttributeClass(a, this.NonActionAttributeSymbol)
                        || IsAttributeClass(a, this.ChildActionOnlyAttributeSymbol))
                    {
                        isAction = false;
                    }
                }
            }

            /// <summary>
            /// Determines if the .NET attribute is of the specified type.
            /// </summary>
            /// <param name="attributeData">The .NET attribute to check.</param>
            /// <param name="symbol">The type of .NET attribute to compare.</param>
            /// <returns>True if .NET attribute's type matches the specified type, false otherwise.</returns>
            private static bool IsAttributeClass(AttributeData attributeData, [NotNullWhen(returnValue: true)] INamedTypeSymbol? symbol)
            {
                return symbol != null && Equals(attributeData.AttributeClass, symbol);
            }
        }
    }
}