1 // Copyright (c) Microsoft. All rights reserved.
2 // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3 
4 using System.Collections;
5 using System.Collections.Generic;
6 using System.Globalization;
7 using System.IO;
8 using System;
9 using System.Text;
10 using System.Xml;
11 
12 using Microsoft.Build.Framework;
13 using Microsoft.Build.BuildEngine.Shared;
14 
15 namespace Microsoft.Build.BuildEngine
16 {
17     /// <summary>
18     /// Evaluates a function expression, such as "Exists('foo')"
19     /// </summary>
20     internal sealed class FunctionCallExpressionNode : OperatorExpressionNode
21     {
22         private ArrayList arguments;
23         private string functionName;
24 
FunctionCallExpressionNode()25         private FunctionCallExpressionNode() { }
26 
FunctionCallExpressionNode(string functionName, ArrayList arguments)27         internal FunctionCallExpressionNode(string functionName, ArrayList arguments)
28         {
29             this.functionName = functionName;
30             this.arguments = arguments;
31         }
32 
33         /// <summary>
34         /// Evaluate node as boolean
35         /// </summary>
BoolEvaluate(ConditionEvaluationState state)36         internal override bool BoolEvaluate(ConditionEvaluationState state)
37         {
38             if (String.Compare(functionName, "exists", StringComparison.OrdinalIgnoreCase) == 0)
39             {
40                 // Check we only have one argument
41                 VerifyArgumentCount(1, state);
42 
43                 // Expand properties and items, and verify the result is an appropriate scalar
44                 string expandedValue = ExpandArgumentForScalarParameter("exists", (GenericExpressionNode)arguments[0], state);
45 
46                 if (Project.PerThreadProjectDirectory != null && !String.IsNullOrEmpty(expandedValue))
47                 {
48                     try
49                     {
50                         expandedValue = Path.GetFullPath(Path.Combine(Project.PerThreadProjectDirectory, expandedValue));
51                     }
52                     catch (Exception e) // Catching Exception, but rethrowing unless it's an IO related exception.
53                     {
54                         if (ExceptionHandling.NotExpectedException(e))
55                             throw;
56 
57                         // Ignore invalid characters or path related exceptions
58 
59                         // We will ignore the PathTooLong exception caused by GetFullPath becasue in single proc this code
60                         // is not executed and the condition is just evaluated to false as File.Exists and Directory.Exists does not throw in this situation.
61                         // To be consistant with that we will return a false in this case also.
62                         // DevDiv Bugs: 46035
63 
64                         return false;
65                     }
66 
67                 }
68 
69                 // Both Exists functions return false if the value is null or empty
70                 return File.Exists(expandedValue) || Directory.Exists(expandedValue);
71             }
72             else if (String.Compare(functionName, "HasTrailingSlash", StringComparison.OrdinalIgnoreCase) == 0)
73             {
74                 // Check we only have one argument
75                 VerifyArgumentCount(1, state);
76 
77                 // Expand properties and items, and verify the result is an appropriate scalar
78                 string expandedValue = ExpandArgumentForScalarParameter("HasTrailingSlash", (GenericExpressionNode)arguments[0], state);
79 
80                 // Is the last character a backslash?
81                 if (expandedValue.Length != 0)
82                 {
83                     char lastCharacter = expandedValue[expandedValue.Length - 1];
84                     // Either back or forward slashes satisfy the function: this is useful for URL's
85                     return (lastCharacter == Path.DirectorySeparatorChar || lastCharacter == Path.AltDirectorySeparatorChar);
86                 }
87                 else
88                 {
89                     return false;
90                 }
91             }
92             // We haven't implemented any other "functions"
93             else
94             {
95                 ProjectErrorUtilities.VerifyThrowInvalidProject(
96                     false,
97                     state.conditionAttribute,
98                     "UndefinedFunctionCall",
99                     state.parsedCondition,
100                     this.functionName);
101 
102                 return false;
103             }
104         }
105 
106         /// <summary>
107         /// Expands properties and items in the argument, and verifies that the result is consistent
108         /// with a scalar parameter type.
109         /// </summary>
110         /// <param name="function">Function name for errors</param>
111         /// <param name="argumentNode">Argument to be expanded</param>
112         /// <returns>Scalar result</returns>
113         /// <owner>danmose</owner>
ExpandArgumentForScalarParameter(string function, GenericExpressionNode argumentNode, ConditionEvaluationState state)114         private string ExpandArgumentForScalarParameter(string function, GenericExpressionNode argumentNode, ConditionEvaluationState state)
115         {
116             string argument = argumentNode.GetUnexpandedValue(state);
117 
118             List<TaskItem> items = state.expanderToUse.ExpandAllIntoTaskItems(argument, state.conditionAttribute);
119 
120             string expandedValue = String.Empty;
121 
122             if (items.Count == 0)
123             {
124                 // Empty argument, that's fine.
125             }
126             else if (items.Count == 1)
127             {
128                 expandedValue = items[0].ItemSpec;
129             }
130             else // too many items for the function
131             {
132                 // We only allow a single item to be passed into a scalar parameter.
133                 ProjectErrorUtilities.VerifyThrowInvalidProject(false,
134                     state.conditionAttribute,
135                     "CannotPassMultipleItemsIntoScalarFunction", function, argument,
136                     state.expanderToUse.ExpandAllIntoString(argument, state.conditionAttribute));
137             }
138 
139             return expandedValue;
140         }
141 
142         /// <summary>
143         /// Check that the number of function arguments is correct.
144         /// </summary>
145         /// <param name="expected"></param>
VerifyArgumentCount(int expected, ConditionEvaluationState state)146         private void VerifyArgumentCount(int expected, ConditionEvaluationState state)
147         {
148             ProjectErrorUtilities.VerifyThrowInvalidProject
149                 (arguments.Count == expected,
150                  state.conditionAttribute,
151                  "IncorrectNumberOfFunctionArguments",
152                  state.parsedCondition,
153                  arguments.Count,
154                  expected);
155         }
156     }
157 }
158