1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Runtime.InteropServices;
6 using System.Collections;
7 using System.DirectoryServices.Interop;
8 using System.Globalization;
9 
10 namespace System.DirectoryServices
11 {
12     /// <devdoc>
13     /// Contains the properties on a <see cref='System.DirectoryServices.DirectoryEntry'/>.
14     /// </devdoc>
15     public class PropertyCollection : IDictionary
16     {
17         private readonly DirectoryEntry _entry;
18         internal readonly Hashtable valueTable = null;
19 
PropertyCollection(DirectoryEntry entry)20         internal PropertyCollection(DirectoryEntry entry)
21         {
22             _entry = entry;
23             Hashtable tempTable = new Hashtable();
24             valueTable = Hashtable.Synchronized(tempTable);
25         }
26 
27         /// <devdoc>
28         /// Gets the property with the given name.
29         /// </devdoc>
30         public PropertyValueCollection this[string propertyName]
31         {
32             get
33             {
34                 if (propertyName == null)
35                     throw new ArgumentNullException("propertyName");
36 
37                 string name = propertyName.ToLower(CultureInfo.InvariantCulture);
38                 if (valueTable.Contains(name))
39                     return (PropertyValueCollection)valueTable[name];
40                 else
41                 {
42                     PropertyValueCollection value = new PropertyValueCollection(_entry, propertyName);
43                     valueTable.Add(name, value);
44                     return value;
45                 }
46             }
47         }
48 
49         /// <devdoc>
50         /// Gets the number of properties available on this entry.
51         /// </devdoc>
52         public int Count
53         {
54             get
55             {
56                 if (!(_entry.AdsObject is UnsafeNativeMethods.IAdsPropertyList))
57                     throw new NotSupportedException(SR.DSCannotCount);
58 
59                 _entry.FillCache("");
60 
61                 UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)_entry.AdsObject;
62 
63                 return propList.PropertyCount;
64             }
65         }
66 
67         /// </devdoc>
68         public ICollection PropertyNames => new KeysCollection(this);
69 
70         public ICollection Values => new ValuesCollection(this);
71 
Contains(string propertyName)72         public bool Contains(string propertyName)
73         {
74             object var;
75             int unmanagedResult = _entry.AdsObject.GetEx(propertyName, out var);
76             if (unmanagedResult != 0)
77             {
78                 //  property not found (IIS provider returns 0x80005006, other provides return 0x8000500D).
79                 if ((unmanagedResult == unchecked((int)0x8000500D)) || (unmanagedResult == unchecked((int)0x80005006)))
80                 {
81                     return false;
82                 }
83                 else
84                 {
85                     throw COMExceptionHelper.CreateFormattedComException(unmanagedResult);
86                 }
87             }
88 
89             return true;
90         }
91 
92         /// <devdoc>
93         /// Copies the elements of this instance into an <see cref='System.Array'/>, starting at a particular index into the array.
94         /// </devdoc>
CopyTo(PropertyValueCollection[] array, int index)95         public void CopyTo(PropertyValueCollection[] array, int index)
96         {
97             ((ICollection)this).CopyTo((Array)array, index);
98         }
99 
100         /// <devdoc>
101         /// Returns an enumerator, which can be used to iterate through the collection.
102         /// </devdoc>
GetEnumerator()103         public IDictionaryEnumerator GetEnumerator()
104         {
105             if (!(_entry.AdsObject is UnsafeNativeMethods.IAdsPropertyList))
106                 throw new NotSupportedException(SR.DSCannotEmunerate);
107 
108             // Once an object has been used for an enumerator once, it can't be used again, because it only
109             // maintains a single cursor. Re-bind to the ADSI object to get a new instance.
110             // That's why we must clone entry here. It will be automatically disposed inside Enumerator.
111             DirectoryEntry entryToUse = _entry.CloneBrowsable();
112             entryToUse.FillCache("");
113 
114             UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)entryToUse.AdsObject;
115 
116             entryToUse.propertiesAlreadyEnumerated = true;
117             return new PropertyEnumerator(_entry, entryToUse);
118         }
119 
120         object IDictionary.this[object key]
121         {
122             get => this[(string)key];
123             set => throw new NotSupportedException(SR.DSPropertySetSupported);
124         }
125 
126         bool IDictionary.IsFixedSize => true;
127 
128         bool IDictionary.IsReadOnly => true;
129 
130         ICollection IDictionary.Keys => new KeysCollection(this);
131 
IDictionary.Add(object key, object value)132         void IDictionary.Add(object key, object value)
133         {
134             throw new NotSupportedException(SR.DSAddNotSupported);
135         }
136 
IDictionary.Clear()137         void IDictionary.Clear()
138         {
139             throw new NotSupportedException(SR.DSClearNotSupported);
140         }
141 
142         bool IDictionary.Contains(object value) => Contains((string)value);
143 
IDictionary.Remove(object key)144         void IDictionary.Remove(object key)
145         {
146             throw new NotSupportedException(SR.DSRemoveNotSupported);
147         }
148 
IEnumerable.GetEnumerator()149         IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
150 
151         bool ICollection.IsSynchronized => false;
152 
153         object ICollection.SyncRoot => this;
154 
ICollection.CopyTo(Array array, Int32 index)155         void ICollection.CopyTo(Array array, Int32 index)
156         {
157             if (array == null)
158                 throw new ArgumentNullException("array");
159 
160             if (array.Rank != 1)
161                 throw new ArgumentException(SR.OnlyAllowSingleDimension, "array");
162 
163             if (index < 0)
164                 throw new ArgumentOutOfRangeException(SR.LessThanZero, "index");
165 
166             if (((index + Count) > array.Length) || ((index + Count) < index))
167                 throw new ArgumentException(SR.DestinationArrayNotLargeEnough);
168 
169             foreach (PropertyValueCollection value in this)
170             {
171                 array.SetValue(value, index);
172                 index++;
173             }
174         }
175 
176         private class PropertyEnumerator : IDictionaryEnumerator, IDisposable
177         {
178             private DirectoryEntry _entry;               // clone (to be disposed)
179             private DirectoryEntry _parentEntry;         // original entry to pass to PropertyValueCollection
180             private string _currentPropName = null;
181 
PropertyEnumerator(DirectoryEntry parent, DirectoryEntry clone)182             public PropertyEnumerator(DirectoryEntry parent, DirectoryEntry clone)
183             {
184                 _entry = clone;
185                 _parentEntry = parent;
186             }
187 
~PropertyEnumerator()188             ~PropertyEnumerator() => Dispose(true);
189 
Dispose()190             public void Dispose()
191             {
192                 Dispose(true);
193                 GC.SuppressFinalize(this);
194             }
195 
Dispose(bool disposing)196             protected virtual void Dispose(bool disposing)
197             {
198                 if (disposing)
199                 {
200                     _entry.Dispose();
201                 }
202             }
203 
204             public object Current => Entry.Value;
205 
206             public DictionaryEntry Entry
207             {
208                 get
209                 {
210                     if (_currentPropName == null)
211                         throw new InvalidOperationException(SR.DSNoCurrentProperty);
212 
213                     return new DictionaryEntry(_currentPropName, new PropertyValueCollection(_parentEntry, _currentPropName));
214                 }
215             }
216 
217             public object Key => Entry.Key;
218 
219             public object Value => Entry.Value;
220 
MoveNext()221             public bool MoveNext()
222             {
223                 object prop;
224                 int hr = 0;
225                 try
226                 {
227                     hr = ((UnsafeNativeMethods.IAdsPropertyList)_entry.AdsObject).Next(out prop);
228                 }
229                 catch (COMException e)
230                 {
231                     hr = e.ErrorCode;
232                     prop = null;
233                 }
234                 if (hr == 0)
235                 {
236                     if (prop != null)
237                         _currentPropName = ((UnsafeNativeMethods.IAdsPropertyEntry)prop).Name;
238                     else
239                         _currentPropName = null;
240 
241                     return true;
242                 }
243                 else
244                 {
245                     _currentPropName = null;
246                     return false;
247                 }
248             }
249 
Reset()250             public void Reset()
251             {
252                 ((UnsafeNativeMethods.IAdsPropertyList)_entry.AdsObject).Reset();
253                 _currentPropName = null;
254             }
255         }
256 
257         private class ValuesCollection : ICollection
258         {
259             protected PropertyCollection props;
260 
ValuesCollection(PropertyCollection props)261             public ValuesCollection(PropertyCollection props)
262             {
263                 this.props = props;
264             }
265 
266             public int Count => props.Count;
267 
268             public bool IsReadOnly => true;
269 
270             public bool IsSynchronized => false;
271 
272             public object SyncRoot => ((ICollection)props).SyncRoot;
273 
CopyTo(Array array, int index)274             public void CopyTo(Array array, int index)
275             {
276                 foreach (object value in this)
277                     array.SetValue(value, index++);
278             }
279 
GetEnumerator()280             public virtual IEnumerator GetEnumerator() => new ValuesEnumerator(props);
281         }
282 
283         private class KeysCollection : ValuesCollection
284         {
KeysCollection(PropertyCollection props)285             public KeysCollection(PropertyCollection props) : base(props)
286             {
287             }
288 
GetEnumerator()289             public override IEnumerator GetEnumerator()
290             {
291                 props._entry.FillCache("");
292                 return new KeysEnumerator(props);
293             }
294         }
295 
296         private class ValuesEnumerator : IEnumerator
297         {
298             private int _currentIndex = -1;
299             protected PropertyCollection propCollection;
300 
ValuesEnumerator(PropertyCollection propCollection)301             public ValuesEnumerator(PropertyCollection propCollection)
302             {
303                 this.propCollection = propCollection;
304             }
305 
306             protected int CurrentIndex
307             {
308                 get
309                 {
310                     if (_currentIndex == -1)
311                         throw new InvalidOperationException(SR.DSNoCurrentValue);
312                     return _currentIndex;
313                 }
314             }
315 
316             public virtual object Current
317             {
318                 get
319                 {
320                     UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)propCollection._entry.AdsObject;
321                     return propCollection[((UnsafeNativeMethods.IAdsPropertyEntry)propList.Item(CurrentIndex)).Name];
322                 }
323             }
324 
MoveNext()325             public bool MoveNext()
326             {
327                 _currentIndex++;
328                 if (_currentIndex >= propCollection.Count)
329                 {
330                     _currentIndex = -1;
331                     return false;
332                 }
333                 else
334                     return true;
335             }
336 
Reset()337             public void Reset() => _currentIndex = -1;
338         }
339 
340         private class KeysEnumerator : ValuesEnumerator
341         {
KeysEnumerator(PropertyCollection collection)342             public KeysEnumerator(PropertyCollection collection) : base(collection)
343             {
344             }
345 
346             public override object Current
347             {
348                 get
349                 {
350                     UnsafeNativeMethods.IAdsPropertyList propList = (UnsafeNativeMethods.IAdsPropertyList)propCollection._entry.AdsObject;
351 
352                     return ((UnsafeNativeMethods.IAdsPropertyEntry)propList.Item(CurrentIndex)).Name;
353                 }
354             }
355         }
356     }
357 }
358