1+ //
2+ // Copyright (c) Microsoft Corporation.
3+ //
4+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
5+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
6+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
7+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
8+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
10+ // THE SOFTWARE.
11+ //
12+
13+ using System ;
14+ using System . Collections ;
15+ using System . Collections . Generic ;
16+ using System . IO ;
17+ using System . Linq ;
18+ using System . Management . Automation . Language ;
19+
20+ namespace Microsoft . Windows . PowerShell . ScriptAnalyzer . Generic
21+ {
22+ public class Settings
23+ {
24+ private List < string > includeRules ;
25+ private List < string > excludeRules ;
26+ private List < string > severities ;
27+ private Dictionary < string , Dictionary < string , object > > ruleArguments ;
28+
29+ public IEnumerable < string > IncludeRules { get { return includeRules ; } }
30+ public IEnumerable < string > ExcludeRules { get { return excludeRules ; } }
31+ public IEnumerable < string > Severity { get { return severities ; } }
32+ public Dictionary < string , Dictionary < string , object > > RuleArguments { get { return ruleArguments ; } }
33+
34+ public Settings ( object settings , Func < string , string > presetResolver )
35+ {
36+ if ( settings == null )
37+ {
38+ throw new ArgumentNullException ( "settings" ) ;
39+ }
40+
41+ var settingsFilePath = settings as string ;
42+
43+ //it can either be a preset or path to a file or a hashtable
44+ if ( settingsFilePath != null )
45+ {
46+ if ( presetResolver != null )
47+ {
48+ var resolvedFilePath = presetResolver ( settingsFilePath ) ;
49+ if ( resolvedFilePath != null )
50+ {
51+ settingsFilePath = resolvedFilePath ;
52+ }
53+ }
54+
55+ if ( File . Exists ( settingsFilePath ) )
56+ {
57+ parseSettingsFile ( settingsFilePath ) ;
58+ }
59+ else
60+ {
61+ throw new ArgumentException ( String . Format ( "File does not exist: {0}" , settingsFilePath ) ) ;
62+ }
63+ }
64+ else
65+ {
66+ var settingsHashtable = settings as Hashtable ;
67+ if ( settingsHashtable != null )
68+ {
69+ parseSettingsHashtable ( settingsHashtable ) ;
70+ }
71+ else
72+ {
73+ throw new ArgumentException ( "Input object should either be a string or a hashtable" ) ;
74+ }
75+ }
76+ }
77+
78+ public Settings ( object settings ) : this ( settings , null )
79+ {
80+ }
81+
82+ /// <summary>
83+ /// Recursively convert hashtable to dictionary
84+ /// </summary>
85+ /// <param name="hashtable"></param>
86+ /// <returns>Dictionary that maps string to object</returns>
87+ private Dictionary < string , object > GetDictionaryFromHashtable ( Hashtable hashtable )
88+ {
89+ var dictionary = new Dictionary < string , object > ( StringComparer . OrdinalIgnoreCase ) ;
90+ foreach ( var obj in hashtable . Keys )
91+ {
92+ string key = obj as string ;
93+ if ( key == null )
94+ {
95+ throw new InvalidDataException ( "key not string" ) ;
96+ // writer.WriteError(
97+ // new ErrorRecord(
98+ // new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.KeyNotString, key)),
99+ // Strings.ConfigurationKeyNotAString,
100+ // ErrorCategory.InvalidData,
101+ // hashtable));
102+ // hasError = true;
103+ }
104+ var valueHashtableObj = hashtable [ obj ] ;
105+ if ( valueHashtableObj == null )
106+ {
107+ throw new InvalidDataException ( "wrong hash table value" ) ;
108+ // writer.WriteError(
109+ // new ErrorRecord(
110+ // new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, valueHashtableObj, key)),
111+ // Strings.WrongConfigurationKey,
112+ // ErrorCategory.InvalidData,
113+ // hashtable));
114+ // hasError = true;
115+ // return null;
116+ }
117+ var valueHashtable = valueHashtableObj as Hashtable ;
118+ if ( valueHashtable == null )
119+ {
120+ dictionary . Add ( key , valueHashtableObj ) ;
121+ }
122+ else
123+ {
124+ dictionary . Add ( key , GetDictionaryFromHashtable ( valueHashtable ) ) ;
125+ }
126+ }
127+ return dictionary ;
128+ }
129+
130+ private bool IsStringOrStringArray ( object val )
131+ {
132+ if ( val is string )
133+ {
134+ return true ;
135+ }
136+
137+ var valArr = val as object [ ] ;
138+ return val == null ? false : valArr . All ( x => x is string ) ;
139+ }
140+
141+ private List < string > GetData ( object val , string key )
142+ {
143+ // value must be either string or or an array of strings
144+ if ( val == null )
145+ {
146+ throw new InvalidDataException (
147+ String . Format (
148+ "value should be a string or string array for {0} key" ,
149+ key ) ) ;
150+ // writer.WriteError(
151+ // new ErrorRecord(
152+ // new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, value, key)),
153+ // Strings.WrongConfigurationKey,
154+ // ErrorCategory.InvalidData,
155+ // profile));
156+ // hasError = true;
157+ // break;
158+ }
159+
160+ List < string > values = new List < string > ( ) ;
161+ var valueStr = val as string ;
162+ if ( valueStr != null )
163+ {
164+ values . Add ( valueStr ) ;
165+ }
166+ else
167+ {
168+ var valueArr = val as object [ ] ;
169+ if ( valueArr != null )
170+ {
171+ foreach ( var item in valueArr )
172+ {
173+ var itemStr = item as string ;
174+ if ( itemStr != null )
175+ {
176+ values . Add ( itemStr ) ;
177+ }
178+ else
179+ {
180+ throw new InvalidDataException ( "array items should be of string type" ) ;
181+ // writer.WriteError(
182+ // new ErrorRecord(
183+ // new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongValueHashTable, val, key)),
184+ // Strings.WrongConfigurationKey,
185+ // ErrorCategory.InvalidData,
186+ // profile));
187+ // hasError = true;
188+ // break;
189+ }
190+ }
191+ }
192+ else
193+ {
194+ throw new InvalidDataException ( "array items should be of string type" ) ;
195+ }
196+ }
197+
198+ return values ;
199+ }
200+
201+ /// <summary>
202+ /// Sets the arguments for consumption by rules
203+ /// </summary>
204+ /// <param name="ruleArgs">A hashtable with rule names as keys</param>
205+ public Dictionary < string , Dictionary < string , object > > ConvertToRuleArgumentType ( object ruleArguments )
206+ {
207+ var ruleArgs = ruleArguments as Dictionary < string , object > ;
208+ if ( ruleArgs == null )
209+ {
210+ throw new ArgumentException (
211+ "input should be a dictionary" ,
212+ "ruleArguments" ) ;
213+ }
214+
215+ if ( ruleArgs . Comparer != StringComparer . OrdinalIgnoreCase )
216+ {
217+ throw new ArgumentException (
218+ "Input dictionary should have OrdinalIgnoreCase comparer." ,
219+ "ruleArguments" ) ;
220+ }
221+
222+ var ruleArgsDict = new Dictionary < string , Dictionary < string , object > > ( StringComparer . OrdinalIgnoreCase ) ;
223+ foreach ( var rule in ruleArgs . Keys )
224+ {
225+ var argsDict = ruleArgs [ rule ] as Dictionary < string , object > ;
226+ if ( argsDict == null )
227+ {
228+ throw new ArgumentException (
229+ "input should be a dictionary" ,
230+ "ruleArguments" ) ;
231+ }
232+ ruleArgsDict [ rule ] = argsDict ;
233+ }
234+
235+ return ruleArgsDict ;
236+ }
237+
238+ private void parseSettingsHashtable ( Hashtable settingsHashtable )
239+ {
240+ HashSet < string > validKeys = new HashSet < string > ( StringComparer . OrdinalIgnoreCase ) ;
241+ var settings = GetDictionaryFromHashtable ( settingsHashtable ) ;
242+ foreach ( var settingKey in settings . Keys )
243+ {
244+ var key = settingKey . ToLower ( ) ;
245+ object val = settings [ key ] ;
246+ switch ( key )
247+ {
248+ case "severity" :
249+ severities = GetData ( val , key ) ;
250+ break ;
251+
252+ case "includerules" :
253+ includeRules = GetData ( val , key ) ;
254+ break ;
255+
256+ case "excluderules" :
257+ excludeRules = GetData ( val , key ) ;
258+ break ;
259+
260+ case "rules" :
261+ ruleArguments = ConvertToRuleArgumentType ( val ) ;
262+ break ;
263+
264+ default :
265+ throw new InvalidDataException ( String . Format ( "Invalid key: {0}" , key ) ) ;
266+ // writer.WriteError(
267+ // new ErrorRecord(
268+ // new InvalidDataException(string.Format(CultureInfo.CurrentCulture, Strings.WrongKeyHashTable, key)),
269+ // Strings.WrongConfigurationKey,
270+ // ErrorCategory.InvalidData,
271+ // profile));
272+ // hasError = true;
273+ // break;
274+ }
275+ }
276+ }
277+
278+ private void parseSettingsFile ( string settingsFilePath )
279+ {
280+ Token [ ] parserTokens = null ;
281+ ParseError [ ] parserErrors = null ;
282+ Ast profileAst = Parser . ParseFile ( settingsFilePath , out parserTokens , out parserErrors ) ;
283+ IEnumerable < Ast > hashTableAsts = profileAst . FindAll ( item => item is HashtableAst , false ) ;
284+
285+ // no hashtable, raise warning
286+ if ( hashTableAsts . Count ( ) == 0 )
287+ {
288+ throw new ArgumentException ( "Given file does not contain a hashtable" ) ;
289+ // writer.WriteError(new ErrorRecord(new ArgumentException(string.Format(CultureInfo.CurrentCulture, Strings.InvalidProfile, profile)),
290+ // Strings.ConfigurationFileHasNoHashTable, ErrorCategory.ResourceUnavailable, profile));
291+ // hasError = true;
292+ }
293+
294+ HashtableAst hashTableAst = hashTableAsts . First ( ) as HashtableAst ;
295+ Hashtable hashtable ;
296+ try
297+ {
298+ hashtable = hashTableAst . SafeGetValue ( ) as Hashtable ;
299+ }
300+ catch ( InvalidOperationException e )
301+ {
302+ throw new ArgumentException ( "input file has invalid hashtable" , e ) ;
303+ }
304+
305+ if ( hashtable == null )
306+ {
307+ throw new ArgumentException ( "input file has invalid hashtable" ) ;
308+ }
309+
310+ parseSettingsHashtable ( hashtable ) ;
311+ }
312+ }
313+ }
0 commit comments