Merge pull request #2454 from tastywheattasteslikechicken/FixVtableAbort
[mono.git] / mcs / class / System.Web / System.Web.Profile / SqlProfileProvider.cs
1 //
2 // System.Web.UI.WebControls.SqlProfileProvider.cs
3 //
4 // Authors:
5 //      Chris Toshok (toshok@ximian.com)
6 //  Vladimir Krasnov (vladimirk@mainsoft.com)
7 //
8 // (C) 2006 Novell, Inc (http://www.novell.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 using System;
31 using System.Data;
32 using System.Data.Common;
33 using System.Collections;
34 using System.Globalization;
35 using System.Configuration;
36 using System.Configuration.Provider;
37 using System.Web.Configuration;
38 using System.Collections.Specialized;
39 using System.IO;
40 using System.Text;
41 using System.Web.Security;
42 using System.Web.Util;
43
44 namespace System.Web.Profile
45 {
46         public class SqlProfileProvider : ProfileProvider
47         {
48                 ConnectionStringSettings connectionString;
49                 DbProviderFactory factory;
50
51                 string applicationName;
52                 bool schemaIsOk = false;
53
54                 public override int DeleteInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
55                 {
56                         using (DbConnection connection = CreateConnection ()) {
57                                 DbCommand command = factory.CreateCommand ();
58                                 command.Connection = connection;
59                                 command.CommandType = CommandType.StoredProcedure;
60                                 command.CommandText = @"aspnet_Profile_DeleteInactiveProfiles";
61
62                                 AddParameter (command, "ApplicationName", ApplicationName);
63                                 AddParameter (command, "ProfileAuthOptions", authenticationOption);
64                                 AddParameter (command, "InactiveSinceDate", userInactiveSinceDate);
65                                 DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
66
67                                 command.ExecuteNonQuery ();
68                                 int retVal = GetReturnValue (returnValue);
69                                 return retVal;
70                         }
71                 }
72
73                 public override int DeleteProfiles (ProfileInfoCollection profiles)
74                 {
75                         if (profiles == null)
76                                 throw new ArgumentNullException ("prfoles");
77                         if (profiles.Count == 0)
78                                 throw new ArgumentException ("prfoles");
79
80                         string [] usernames = new string [profiles.Count];
81
82                         int i = 0;
83                         foreach (ProfileInfo pi in profiles) {
84                                 if (pi.UserName == null)
85                                         throw new ArgumentNullException ("element in profiles collection is null");
86
87                                 if (pi.UserName.Length == 0 || pi.UserName.Length > 256 || pi.UserName.IndexOf (',') != -1)
88                                         throw new ArgumentException ("element in profiles collection in illegal format");
89
90                                 usernames [i++] = pi.UserName;
91                         }
92
93                         return DeleteProfilesInternal (usernames);
94                 }
95
96                 public override int DeleteProfiles (string [] usernames)
97                 {
98                         if (usernames == null)
99                                 throw new ArgumentNullException ("usernames");
100
101                         Hashtable users = new Hashtable ();
102                         foreach (string username in usernames) {
103                                 if (username == null)
104                                         throw new ArgumentNullException ("element in usernames array is null");
105
106                                 if (username.Length == 0 || username.Length > 256 || username.IndexOf (',') != -1)
107                                         throw new ArgumentException ("element in usernames array in illegal format");
108
109                                 if (users.ContainsKey(username))
110                                         throw new ArgumentException ("duplicate element in usernames array");
111
112                                 users.Add (username, username);
113                         }
114
115                         return DeleteProfilesInternal (usernames);
116                 }
117
118                 int DeleteProfilesInternal (string [] usernames)
119                 {
120                         using (DbConnection connection = CreateConnection ()) {
121                                 DbCommand command = factory.CreateCommand ();
122                                 command.Connection = connection;
123                                 command.CommandType = CommandType.StoredProcedure;
124                                 command.CommandText = @"aspnet_Profile_DeleteProfiles";
125
126                                 AddParameter (command, "ApplicationName", ApplicationName);
127                                 AddParameter (command, "UserNames", string.Join (",", usernames));
128                                 DbParameter returnValue = AddParameter (command, null, ParameterDirection.ReturnValue, null);
129
130                                 command.ExecuteNonQuery ();
131                                 int retVal = GetReturnValue (returnValue);
132                                 return retVal;
133                         }
134                 }
135
136                 public override ProfileInfoCollection FindInactiveProfilesByUserName (ProfileAuthenticationOption authenticationOption,
137                                                                                       string usernameToMatch,
138                                                                                       DateTime userInactiveSinceDate,
139                                                                                       int pageIndex,
140                                                                                       int pageSize,
141                                                                                       out int totalRecords)
142                 {
143                         CheckParam ("usernameToMatch", usernameToMatch, 256);
144                         if (pageIndex < 0)
145                                 throw new ArgumentException("pageIndex is less than zero");
146                         if (pageSize < 1)
147                                 throw new ArgumentException ("pageIndex is less than one");
148                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
149                                 throw new ArgumentException ("pageIndex and pageSize are too large");
150
151                         using (DbConnection connection = CreateConnection ()) {
152                                 DbCommand command = factory.CreateCommand ();
153                                 command.Connection = connection;
154                                 command.CommandType = CommandType.StoredProcedure;
155                                 command.CommandText = @"aspnet_Profile_GetProfiles";
156
157                                 AddParameter (command, "ApplicationName", ApplicationName);
158                                 AddParameter (command, "ProfileAuthOptions", authenticationOption);
159                                 AddParameter (command, "PageIndex", pageIndex);
160                                 AddParameter (command, "PageSize", pageSize);
161                                 AddParameter (command, "UserNameToMatch", usernameToMatch);
162                                 AddParameter (command, "InactiveSinceDate", userInactiveSinceDate);
163
164                                 using (DbDataReader reader = command.ExecuteReader ()) {
165                                         return BuildProfileInfoCollection (reader, out totalRecords);
166                                 }
167                         }
168                 }
169
170                 public override ProfileInfoCollection FindProfilesByUserName (ProfileAuthenticationOption authenticationOption,
171                                                                               string usernameToMatch,
172                                                                               int pageIndex,
173                                                                               int pageSize,
174                                                                               out int totalRecords)
175                 {
176                         CheckParam ("usernameToMatch", usernameToMatch, 256);
177                         if (pageIndex < 0)
178                                 throw new ArgumentException ("pageIndex is less than zero");
179                         if (pageSize < 1)
180                                 throw new ArgumentException ("pageIndex is less than one");
181                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
182                                 throw new ArgumentException ("pageIndex and pageSize are too large");
183
184                         using (DbConnection connection = CreateConnection ()) {
185                                 DbCommand command = factory.CreateCommand ();
186                                 command.Connection = connection;
187                                 command.CommandType = CommandType.StoredProcedure;
188                                 command.CommandText = @"aspnet_Profile_GetProfiles";
189
190                                 AddParameter (command, "ApplicationName", ApplicationName);
191                                 AddParameter (command, "ProfileAuthOptions", authenticationOption);
192                                 AddParameter (command, "PageIndex", pageIndex);
193                                 AddParameter (command, "PageSize", pageSize);
194                                 AddParameter (command, "UserNameToMatch", usernameToMatch);
195                                 AddParameter (command, "InactiveSinceDate", null);
196
197                                 using (DbDataReader reader = command.ExecuteReader ()) {
198                                         return BuildProfileInfoCollection (reader, out totalRecords);
199                                 }
200                         }
201                 }
202
203                 public override ProfileInfoCollection GetAllInactiveProfiles (ProfileAuthenticationOption authenticationOption,
204                                                                               DateTime userInactiveSinceDate,
205                                                                               int pageIndex,
206                                                                               int pageSize,
207                                                                               out int totalRecords)
208                 {
209                         if (pageIndex < 0)
210                                 throw new ArgumentException ("pageIndex is less than zero");
211                         if (pageSize < 1)
212                                 throw new ArgumentException ("pageIndex is less than one");
213                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
214                                 throw new ArgumentException ("pageIndex and pageSize are too large");
215                         
216                         using (DbConnection connection = CreateConnection ()) {
217                                 DbCommand command = factory.CreateCommand ();
218                                 command.Connection = connection;
219                                 command.CommandType = CommandType.StoredProcedure;
220                                 command.CommandText = @"aspnet_Profile_GetProfiles";
221
222                                 AddParameter (command, "ApplicationName", ApplicationName);
223                                 AddParameter (command, "ProfileAuthOptions", authenticationOption);
224                                 AddParameter (command, "PageIndex", pageIndex);
225                                 AddParameter (command, "PageSize", pageSize);
226                                 AddParameter (command, "UserNameToMatch", null);
227                                 AddParameter (command, "InactiveSinceDate", null);
228
229                                 using (DbDataReader reader = command.ExecuteReader ()) {
230                                         return BuildProfileInfoCollection (reader, out totalRecords);
231                                 }
232                         }
233                 }
234
235                 public override ProfileInfoCollection GetAllProfiles (ProfileAuthenticationOption authenticationOption,
236                                                                       int pageIndex,
237                                                                       int pageSize,
238                                                                       out int totalRecords)
239                 {
240                         if (pageIndex < 0)
241                                 throw new ArgumentException ("pageIndex is less than zero");
242                         if (pageSize < 1)
243                                 throw new ArgumentException ("pageIndex is less than one");
244                         if (pageIndex * pageSize + pageSize - 1 > Int32.MaxValue)
245                                 throw new ArgumentException ("pageIndex and pageSize are too large");
246
247                         using (DbConnection connection = CreateConnection ()) {
248                                 DbCommand command = factory.CreateCommand ();
249                                 command.Connection = connection;
250                                 command.CommandType = CommandType.StoredProcedure;
251                                 command.CommandText = @"aspnet_Profile_GetProfiles";
252
253                                 AddParameter (command, "ApplicationName", ApplicationName);
254                                 AddParameter (command, "ProfileAuthOptions", authenticationOption);
255                                 AddParameter (command, "PageIndex", pageIndex);
256                                 AddParameter (command, "PageSize", pageSize);
257                                 AddParameter (command, "UserNameToMatch", null);
258                                 AddParameter (command, "InactiveSinceDate", null);
259
260                                 using (DbDataReader reader = command.ExecuteReader ()) {
261                                         return BuildProfileInfoCollection (reader, out totalRecords);
262                                 }
263                         }
264                 }
265
266                 public override int GetNumberOfInactiveProfiles (ProfileAuthenticationOption authenticationOption, DateTime userInactiveSinceDate)
267                 {
268                         using (DbConnection connection = CreateConnection ()) {
269
270                                 DbCommand command = factory.CreateCommand ();
271                                 command.Connection = connection;
272                                 command.CommandType = CommandType.StoredProcedure;
273                                 command.CommandText = @"aspnet_Profile_GetNumberOfInactiveProfiles";
274
275                                 AddParameter (command, "ApplicationName", ApplicationName);
276                                 AddParameter (command, "ProfileAuthOptions", authenticationOption);
277                                 AddParameter (command, "InactiveSinceDate", userInactiveSinceDate);
278
279                                 int returnValue = 0;
280                                 using (DbDataReader reader = command.ExecuteReader ()) {
281                                         if (reader.Read ())
282                                                 returnValue = reader.GetInt32 (0);
283                                 }
284                                 return returnValue;
285                         }
286                 }
287
288                 public override SettingsPropertyValueCollection GetPropertyValues (SettingsContext sc, SettingsPropertyCollection properties)
289                 {
290                         SettingsPropertyValueCollection settings = new SettingsPropertyValueCollection ();
291
292                         if (properties.Count == 0)
293                                 return settings;
294
295                         foreach (SettingsProperty property in properties) {
296                                 if (property.SerializeAs == SettingsSerializeAs.ProviderSpecific)
297                                         if (property.PropertyType.IsPrimitive || property.PropertyType == typeof (String))
298                                                 property.SerializeAs = SettingsSerializeAs.String;
299                                         else
300                                                 property.SerializeAs = SettingsSerializeAs.Xml;
301
302                                 settings.Add (new SettingsPropertyValue (property));
303                         }
304
305                         string username = (string) sc ["UserName"];
306                         using (DbConnection connection = CreateConnection ()) {
307                                 DbCommand command = factory.CreateCommand ();
308                                 command.Connection = connection;
309                                 command.CommandType = CommandType.StoredProcedure;
310                                 command.CommandText = @"aspnet_Profile_GetProperties";
311
312                                 AddParameter (command, "ApplicationName", ApplicationName);
313                                 AddParameter (command, "UserName", username);
314                                 AddParameter (command, "CurrentTimeUtc", DateTime.UtcNow);
315
316                                 using (DbDataReader reader = command.ExecuteReader ()) {
317                                         if (reader.Read ()) {
318                                                 string allnames = reader.GetString (0);
319                                                 string allvalues = reader.GetString (1);
320                                                 int binaryLen = (int) reader.GetBytes (2, 0, null, 0, 0);
321                                                 byte [] binaryvalues = new byte [binaryLen];
322                                                 reader.GetBytes (2, 0, binaryvalues, 0, binaryLen);
323
324                                                 DecodeProfileData (allnames, allvalues, binaryvalues, settings);
325                                         }
326                                 }
327                         }
328
329                         return settings;
330                 }
331                 
332                 public override void SetPropertyValues (SettingsContext sc, SettingsPropertyValueCollection properties)
333                 {
334                         string username = (string) sc ["UserName"];
335                         bool isAnonymous = !(bool) sc ["IsAuthenticated"];
336
337                         string names = String.Empty;
338                         string values = String.Empty;
339                         byte [] buf = null;
340
341                         EncodeProfileData (ref names, ref values, ref buf, properties, !isAnonymous);
342
343                         using (DbConnection connection = CreateConnection ()) {
344                                 DbCommand command = factory.CreateCommand ();
345                                 command.Connection = connection;
346                                 command.CommandType = CommandType.StoredProcedure;
347                                 command.CommandText = @"aspnet_Profile_SetProperties";
348
349                                 AddParameter (command, "ApplicationName", ApplicationName);
350                                 AddParameter (command, "PropertyNames", names);
351                                 AddParameter (command, "PropertyValuesString", values);
352                                 AddParameter (command, "PropertyValuesBinary", buf);
353                                 AddParameter (command, "UserName", username);
354                                 AddParameter (command, "IsUserAnonymous", isAnonymous);
355                                 AddParameter (command, "CurrentTimeUtc", DateTime.UtcNow);
356
357                                 // Return value
358                                 AddParameter (command, null, ParameterDirection.ReturnValue, null);
359
360                                 command.ExecuteNonQuery ();
361                                 return;
362                         }
363                 }
364
365                 public override void Initialize (string name, NameValueCollection config)
366                 {
367                         if (config == null)
368                                 throw new ArgumentNullException ("config");
369
370                         base.Initialize (name, config);
371
372                         applicationName = GetStringConfigValue (config, "applicationName", "/");
373
374                         string connectionStringName = config ["connectionStringName"];
375
376                         if (applicationName.Length > 256)
377                                 throw new ProviderException ("The ApplicationName attribute must be 256 characters long or less.");
378                         if (connectionStringName == null || connectionStringName.Length == 0)
379                                 throw new ProviderException ("The ConnectionStringName attribute must be present and non-zero length.");
380
381                         connectionString = WebConfigurationManager.ConnectionStrings [connectionStringName];
382                         factory = connectionString == null || String.IsNullOrEmpty (connectionString.ProviderName) ?
383                                 System.Data.SqlClient.SqlClientFactory.Instance :
384                                 ProvidersHelper.GetDbProviderFactory (connectionString.ProviderName);
385                 }
386
387                 public override string ApplicationName {
388                         get { return applicationName; }
389                         set { applicationName = value; }
390                 }
391
392                 DbConnection CreateConnection ()
393                 {
394                         if (!schemaIsOk && !(schemaIsOk = AspNetDBSchemaChecker.CheckMembershipSchemaVersion (factory, connectionString.ConnectionString, "profile", "1")))
395                                 throw new ProviderException ("Incorrect ASP.NET DB Schema Version.");
396
397                         DbConnection connection = factory.CreateConnection ();
398                         connection.ConnectionString = connectionString.ConnectionString;
399
400                         connection.Open ();
401                         return connection;
402                 }
403
404                 DbParameter AddParameter (DbCommand command, string parameterName, object parameterValue)
405                 {
406                         return AddParameter (command, parameterName, ParameterDirection.Input, parameterValue);
407                 }
408
409                 DbParameter AddParameter (DbCommand command, string parameterName, ParameterDirection direction, object parameterValue)
410                 {
411                         DbParameter dbp = command.CreateParameter ();
412                         dbp.ParameterName = parameterName;
413                         dbp.Value = parameterValue;
414                         dbp.Direction = direction;
415                         command.Parameters.Add (dbp);
416                         return dbp;
417                 }
418
419                 void CheckParam (string pName, string p, int length)
420                 {
421                         if (p == null)
422                                 throw new ArgumentNullException (pName);
423                         if (p.Length == 0 || p.Length > length || p.IndexOf (',') != -1)
424                                 throw new ArgumentException (String.Concat ("invalid format for ", pName));
425                 }
426                 
427                 static int GetReturnValue (DbParameter returnValue)
428                 {
429                         object value = returnValue.Value;
430                         return value is int ? (int) value : -1;
431                 }
432
433                 ProfileInfo ReadProfileInfo (DbDataReader reader)
434                 {
435                         ProfileInfo pi = null;
436                         try {
437                                 string username = reader.GetString (0);
438                                 bool anonymous = reader.GetBoolean (1);
439                                 DateTime lastUpdate = reader.GetDateTime (2);
440                                 DateTime lastActivity = reader.GetDateTime (3);
441                                 int size = reader.GetInt32 (4);
442
443                                 pi = new ProfileInfo (username, anonymous, lastActivity, lastUpdate, size);
444                         }
445                         catch {
446                         }
447
448                         return pi;
449                 }
450
451                 ProfileInfoCollection BuildProfileInfoCollection (DbDataReader reader, out int totalRecords)
452                 {
453                         ProfileInfoCollection pic = new ProfileInfoCollection ();
454                         while (reader.Read ()) {
455                                 ProfileInfo pi = ReadProfileInfo (reader);
456                                 if (pi != null)
457                                         pic.Add (pi);
458                         }
459                         totalRecords = 0;
460                         if (reader.NextResult ()) {
461                                 if (reader.Read ())
462                                         totalRecords = reader.GetInt32 (0);
463                         }
464                         return pic;
465                 }
466
467                 string GetStringConfigValue (NameValueCollection config, string name, string def)
468                 {
469                         string retVal = def;
470                         string val = config [name];
471                         if (val != null)
472                                 retVal = val;
473                         return retVal;
474                 }
475
476                 // Helper methods
477                 void DecodeProfileData (string allnames, string values, byte [] buf, SettingsPropertyValueCollection properties)
478                 {
479                         if (allnames == null || values == null || buf == null || properties == null)
480                                 return;
481
482                         string [] names = allnames.Split (':');
483                         for (int i = 0; i < names.Length; i += 4) {
484                                 string name = names [i];
485                                 SettingsPropertyValue pp = properties [name];
486
487                                 if (pp == null)
488                                         continue;
489
490                                 int pos = Int32.Parse (names [i + 2], Helpers.InvariantCulture);
491                                 int len = Int32.Parse (names [i + 3], Helpers.InvariantCulture);
492
493                                 if (len == -1 && !pp.Property.PropertyType.IsValueType) {
494                                         pp.PropertyValue = null;
495                                         pp.IsDirty = false;
496                                         pp.Deserialized = true;
497                                 }
498                                 else if (names [i + 1] == "S" && pos >= 0 && len > 0 && values.Length >= pos + len) {
499                                         pp.SerializedValue = values.Substring (pos, len);
500                                 }
501                                 else if (names [i + 1] == "B" && pos >= 0 && len > 0 && buf.Length >= pos + len) {
502                                         byte [] buf2 = new byte [len];
503                                         Buffer.BlockCopy (buf, pos, buf2, 0, len);
504                                         pp.SerializedValue = buf2;
505                                 }
506                         }
507                 }
508
509                 void EncodeProfileData (ref string allNames, ref string allValues, ref byte [] buf, SettingsPropertyValueCollection properties, bool userIsAuthenticated)
510                 {
511                         StringBuilder names = new StringBuilder ();
512                         StringBuilder values = new StringBuilder ();
513                         MemoryStream stream = new MemoryStream ();
514
515                         try {
516                                 foreach (SettingsPropertyValue pp in properties) {
517                                         if (!userIsAuthenticated && !(bool) pp.Property.Attributes ["AllowAnonymous"])
518                                                 continue;
519
520                                         if (!pp.IsDirty && pp.UsingDefaultValue)
521                                                 continue;
522
523                                         int len = 0, pos = 0;
524                                         string propValue = null;
525
526                                         if (pp.Deserialized && pp.PropertyValue == null)
527                                                 len = -1;
528                                         else {
529                                                 object sVal = pp.SerializedValue;
530
531                                                 if (sVal == null)
532                                                         len = -1;
533                                                 else if (sVal is string) {
534                                                         propValue = (string) sVal;
535                                                         len = propValue.Length;
536                                                         pos = values.Length;
537                                                 }
538                                                 else {
539                                                         byte [] b2 = (byte []) sVal;
540                                                         pos = (int) stream.Position;
541                                                         stream.Write (b2, 0, b2.Length);
542                                                         stream.Position = pos + b2.Length;
543                                                         len = b2.Length;
544                                                 }
545                                         }
546
547                                         names.Append (pp.Name + ":" + ((propValue != null) ? "S" : "B") + ":" + pos.ToString (Helpers.InvariantCulture) + ":" + len.ToString (Helpers.InvariantCulture) + ":");
548
549                                         if (propValue != null)
550                                                 values.Append (propValue);
551                                 }
552                                 buf = stream.ToArray ();
553                         }
554                         finally {
555                                 if (stream != null)
556                                         stream.Close ();
557                         }
558
559                         allNames = names.ToString ();
560                         allValues = values.ToString ();
561                 }
562         }
563 }
564