/
PropertyValueCollection.cs
341 lines (307 loc) · 12.2 KB
/
PropertyValueCollection.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
// 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;
using System.DirectoryServices.Interop;
namespace System.DirectoryServices
{
/// <devdoc>
/// Holds a collection of values for a multi-valued property.
/// </devdoc>
public class PropertyValueCollection : CollectionBase
{
internal enum UpdateType
{
Add = 0,
Delete = 1,
Update = 2,
None = 3
}
private readonly DirectoryEntry _entry;
private UpdateType _updateType = UpdateType.None;
private readonly ArrayList _changeList;
private readonly bool _allowMultipleChange;
private readonly bool _needNewBehavior;
internal PropertyValueCollection(DirectoryEntry entry, string propertyName)
{
_entry = entry;
PropertyName = propertyName;
PopulateList();
ArrayList tempList = new ArrayList();
_changeList = ArrayList.Synchronized(tempList);
_allowMultipleChange = entry.allowMultipleChange;
string tempPath = entry.Path;
if (tempPath == null || tempPath.Length == 0)
{
// user does not specify path, so we bind to default naming context using LDAP provider.
_needNewBehavior = true;
}
else
{
if (tempPath.StartsWith("LDAP:", StringComparison.Ordinal))
_needNewBehavior = true;
}
}
public object? this[int index]
{
get => List[index];
set
{
if (_needNewBehavior && !_allowMultipleChange)
throw new NotSupportedException();
else
{
List[index] = value;
}
}
}
public string PropertyName { get; }
public object? Value
{
get
{
if (this.Count == 0)
return null;
else if (this.Count == 1)
return List[0];
else
{
object[] objectArray = new object[this.Count];
List.CopyTo(objectArray, 0);
return objectArray;
}
}
set
{
try
{
this.Clear();
}
catch (System.Runtime.InteropServices.COMException e)
{
if (e.ErrorCode != unchecked((int)0x80004005) || (value == null))
// WinNT provider throws E_FAIL when null value is specified though actually ADS_PROPERTY_CLEAR option is used, need to catch exception
// here. But at the same time we don't want to catch the exception if user explicitly sets the value to null.
throw;
}
if (value == null)
return;
// we could not do Clear and Add, we have to bypass the existing collection cache
_changeList.Clear();
if (value is Array)
{
// byte[] is a special case, we will follow what ADSI is doing, it must be an octet string. So treat it as a single valued attribute
if (value is byte[])
_changeList.Add(value);
else if (value is object[])
_changeList.AddRange((object[])value);
else
{
//Need to box value type array elements.
object[] objArray = new object[((Array)value).Length];
((Array)value).CopyTo(objArray, 0);
_changeList.AddRange((object[])objArray);
}
}
else
_changeList.Add(value);
object?[] allValues = new object[_changeList.Count];
_changeList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Update, PropertyName, allValues);
_entry.CommitIfNotCaching();
// populate the new context
PopulateList();
}
}
/// <devdoc>
/// Appends the value to the set of values for this property.
/// </devdoc>
public int Add(object? value) => List.Add(value);
/// <devdoc>
/// Appends the values to the set of values for this property.
/// </devdoc>
public void AddRange(object?[] value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
for (int i = 0; ((i) < (value.Length)); i = ((i) + (1)))
{
this.Add(value[i]);
}
}
/// <devdoc>
/// Appends the values to the set of values for this property.
/// </devdoc>
public void AddRange(PropertyValueCollection value)
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
int currentCount = value.Count;
for (int i = 0; i < currentCount; i = ((i) + (1)))
{
this.Add(value[i]);
}
}
public bool Contains(object? value) => List.Contains(value);
/// <devdoc>
/// Copies the elements of this instance into an <see cref='System.Array'/>,
/// starting at a particular index into the given <paramref name="array"/>.
/// </devdoc>
public void CopyTo(object?[] array, int index)
{
List.CopyTo(array, index);
}
public int IndexOf(object? value) => List.IndexOf(value);
public void Insert(int index, object? value) => List.Insert(index, value);
private void PopulateList()
{
//No need to fill the cache here, when GetEx is calles, an implicit
//call to GetInfo will be called against an uninitialized property
//cache. Which is exactly what FillCache does.
//entry.FillCache(propertyName);
object? var;
int unmanagedResult = _entry.AdsObject.GetEx(PropertyName, out var);
if (unmanagedResult != 0)
{
// property not found (IIS provider returns 0x80005006, other provides return 0x8000500D).
if ((unmanagedResult == unchecked((int)0x8000500D)) || (unmanagedResult == unchecked((int)0x80005006)))
{
return;
}
else
{
throw COMExceptionHelper.CreateFormattedComException(unmanagedResult);
}
}
if (var is ICollection)
InnerList.AddRange((ICollection)var);
else
InnerList.Add(var);
}
/// <devdoc>
/// Removes the value from the collection.
/// </devdoc>
public void Remove(object? value)
{
if (_needNewBehavior)
{
try
{
List.Remove(value);
}
catch (ArgumentException)
{
// exception is thrown because value does not exist in the current cache, but it actually might do exist just because it is a very
// large multivalued attribute, the value has not been downloaded yet.
OnRemoveComplete(0, value);
}
}
else
List.Remove(value);
}
protected override void OnClearComplete()
{
if (_needNewBehavior && !_allowMultipleChange && _updateType != UpdateType.None && _updateType != UpdateType.Update)
{
throw new InvalidOperationException(SR.DSPropertyValueSupportOneOperation);
}
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Clear, PropertyName, null);
_updateType = UpdateType.Update;
try
{
_entry.CommitIfNotCaching();
}
catch (System.Runtime.InteropServices.COMException e)
{
// On ADSI 2.5 if property has not been assigned any value before,
// then IAds::SetInfo() in CommitIfNotCaching returns bad HREsult 0x8007200A, which we ignore.
if (e.ErrorCode != unchecked((int)0x8007200A)) // ERROR_DS_NO_ATTRIBUTE_OR_VALUE
throw;
}
}
protected override void OnInsertComplete(int index, object? value)
{
if (_needNewBehavior)
{
if (!_allowMultipleChange)
{
if (_updateType != UpdateType.None && _updateType != UpdateType.Add)
{
throw new InvalidOperationException(SR.DSPropertyValueSupportOneOperation);
}
_changeList.Add(value);
object[] allValues = new object[_changeList.Count];
_changeList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Append, PropertyName, allValues);
_updateType = UpdateType.Add;
}
else
{
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Append, PropertyName, new object?[] { value });
}
}
else
{
object[] allValues = new object[InnerList.Count];
InnerList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Update, PropertyName, allValues);
}
_entry.CommitIfNotCaching();
}
protected override void OnRemoveComplete(int index, object? value)
{
if (_needNewBehavior)
{
if (!_allowMultipleChange)
{
if (_updateType != UpdateType.None && _updateType != UpdateType.Delete)
{
throw new InvalidOperationException(SR.DSPropertyValueSupportOneOperation);
}
_changeList.Add(value);
object?[] allValues = new object[_changeList.Count];
_changeList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Delete, PropertyName, allValues);
_updateType = UpdateType.Delete;
}
else
{
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Delete, PropertyName, new object?[] { value });
}
}
else
{
object?[] allValues = new object[InnerList.Count];
InnerList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Update, PropertyName, allValues);
}
_entry.CommitIfNotCaching();
}
protected override void OnSetComplete(int index, object? oldValue, object? newValue)
{
// no need to consider the not allowing accumulative change case as it does not support Set
if (Count <= 1)
{
_entry.AdsObject.Put(PropertyName, newValue);
}
else
{
if (_needNewBehavior)
{
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Delete, PropertyName, new object?[] { oldValue });
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Append, PropertyName, new object?[] { newValue });
}
else
{
object?[] allValues = new object[InnerList.Count];
InnerList.CopyTo(allValues, 0);
_entry.AdsObject.PutEx((int)AdsPropertyOperation.Update, PropertyName, allValues);
}
}
_entry.CommitIfNotCaching();
}
}
}