Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data / System / Data / OleDb / oledbconnectionstring.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="oledbconnectionstring.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 // <owner current="true" primary="true">Microsoft</owner>
6 // <owner current="true" primary="false">Microsoft</owner>
7 //------------------------------------------------------------------------------
8
9 namespace System.Data.OleDb {
10
11     using System;
12     using System.Collections;
13     using System.Collections.Generic;
14     using System.Collections.Specialized;
15     using System.Data;
16     using System.Data.Common;
17     using System.Diagnostics;
18     using System.Globalization;
19     using System.IO;
20     using System.Security;
21     using System.Security.Permissions;
22     using System.Text;
23     using Microsoft.Win32;
24     using System.Runtime.Versioning;
25
26     internal struct SchemaSupport {
27         internal Guid _schemaRowset;
28         internal int  _restrictions;
29     }
30
31     internal sealed class OleDbConnectionString : DbConnectionOptions {
32         // instances of this class are intended to be immutable, i.e readonly
33         // used by pooling classes so it is much easier to verify correctness
34         // when not worried about the class being modified during execution
35
36         internal static class KEY {
37             internal const string Asynchronous_Processing = "asynchronous processing";
38             internal const string Connect_Timeout         = "connect timeout";
39             internal const string Data_Provider           = "data provider";
40             internal const string Data_Source             = "data source";
41             internal const string Extended_Properties     = "extended properties";
42             internal const string File_Name               = "file name";
43             internal const string Initial_Catalog         = "initial catalog";
44             internal const string Ole_DB_Services         = "ole db services";
45             internal const string Persist_Security_Info   = "persist security info";
46             internal const string Prompt                  = "prompt";
47             internal const string Provider                = "provider";
48             internal const string RemoteProvider          = "remote provider";
49             internal const string WindowHandle            = "window handle";
50         }
51
52         // registry key and dword value entry for udl pooling
53         private static class UDL {
54             internal const string Header   = "\xfeff[oledb]\r\n; Everything after this line is an OLE DB initstring\r\n";
55             internal const string Location = "SOFTWARE\\Microsoft\\DataAccess\\Udl Pooling";
56             internal const string Pooling  = "Cache Size";
57
58             static internal volatile bool      _PoolSizeInit;
59             static internal int                _PoolSize;
60
61             static internal volatile Dictionary<string,string> _Pool;
62             static internal object             _PoolLock = new object();
63         }
64
65         private static class VALUES {
66             internal const string NoPrompt = "noprompt";
67         }
68
69         // set during ctor
70         internal readonly bool PossiblePrompt;
71         internal readonly string ActualConnectionString; // cached value passed to GetDataSource
72
73         private readonly string _expandedConnectionString;
74
75         internal SchemaSupport[] _schemaSupport;
76
77         internal int _sqlSupport;
78         internal bool _supportMultipleResults;
79         internal bool _supportIRow;
80         internal bool _hasSqlSupport;
81         internal bool _hasSupportMultipleResults, _hasSupportIRow;
82
83         private int _oledbServices;
84
85         // these are cached delegates (per unique connectionstring)
86         internal UnsafeNativeMethods.IUnknownQueryInterface        DangerousDataSourceIUnknownQueryInterface;
87         internal UnsafeNativeMethods.IDBInitializeInitialize       DangerousIDBInitializeInitialize;
88         internal UnsafeNativeMethods.IDBCreateSessionCreateSession DangerousIDBCreateSessionCreateSession;
89         internal UnsafeNativeMethods.IDBCreateCommandCreateCommand DangerousIDBCreateCommandCreateCommand;
90
91         // since IDBCreateCommand interface may not be supported for a particular provider (only IOpenRowset)
92         // we cache that fact rather than call QueryInterface on every call to Open
93         internal bool HaveQueriedForCreateCommand;
94
95         // SxS: if user specifies a value for "File Name=" (UDL) in connection string, OleDbConnectionString will load the connection string
96         // from the UDL file. The UDL file is opened as FileMode.Open, FileAccess.Read, FileShare.Read, allowing concurrent access to it.
97         [ResourceExposure(ResourceScope.None)]
98         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
99         internal OleDbConnectionString(string connectionString, bool validate) : base(connectionString) {
100             string prompt = this[KEY.Prompt];
101             PossiblePrompt = ((!ADP.IsEmpty(prompt) && (0 != String.Compare(prompt, VALUES.NoPrompt, StringComparison.OrdinalIgnoreCase)))
102                               || !ADP.IsEmpty(this[KEY.WindowHandle]));
103
104             if (!IsEmpty) {
105                 string udlConnectionString = null;
106                 if (!validate) {
107                     int position = 0;
108                     string udlFileName = null;
109                     _expandedConnectionString = ExpandDataDirectories(ref udlFileName, ref position);
110
111                     if (!ADP.IsEmpty(udlFileName)) { // fail via new FileStream vs. GetFullPath
112                         udlFileName = ADP.GetFullPath(udlFileName); // MDAC 82833
113                     }
114                     if (null != udlFileName) {
115                         udlConnectionString = LoadStringFromStorage(udlFileName);
116
117                         if (!ADP.IsEmpty(udlConnectionString)) {
118                             _expandedConnectionString = _expandedConnectionString.Substring(0, position) + udlConnectionString + ';' + _expandedConnectionString.Substring(position);
119                         }
120                     }
121                 }
122                 if (validate || ADP.IsEmpty(udlConnectionString)) {
123                     ActualConnectionString = ValidateConnectionString(connectionString);
124                 }
125             }
126         }
127
128         internal int ConnectTimeout {
129             get { return base.ConvertValueToInt32(KEY.Connect_Timeout, ADP.DefaultConnectionTimeout); }
130         }
131
132         internal string DataSource {
133             get { return base.ConvertValueToString(KEY.Data_Source, ADP.StrEmpty); }
134         }
135
136         internal string InitialCatalog {
137             get { return base.ConvertValueToString(KEY.Initial_Catalog, ADP.StrEmpty); }
138         }
139
140         internal string Provider {
141             get {
142                 Debug.Assert(!ADP.IsEmpty(this[KEY.Provider]), "no Provider");
143                 return this[KEY.Provider];
144             }
145         }
146
147         internal int OleDbServices {
148             get {
149                 return _oledbServices;
150             }
151         }
152
153         internal SchemaSupport[] SchemaSupport { // OleDbConnection.GetSchemaRowsetInformation
154             get { return _schemaSupport; }
155             set { _schemaSupport = value; }
156         }
157
158         protected internal override System.Security.PermissionSet CreatePermissionSet() {
159             System.Security.PermissionSet permissionSet;
160             if (PossiblePrompt) {
161                 permissionSet = new NamedPermissionSet("FullTrust");
162             }
163             else {
164                 permissionSet = new System.Security.PermissionSet(System.Security.Permissions.PermissionState.None);
165                 permissionSet.AddPermission(new OleDbPermission(this));
166             }
167             return permissionSet;
168         }
169
170         protected internal override string Expand() {
171             if (null != _expandedConnectionString) {
172                 return _expandedConnectionString;
173             }
174             else {
175                 return base.Expand();
176             }
177         }
178
179         internal int GetSqlSupport(OleDbConnection connection) {
180             int sqlSupport = _sqlSupport;
181             if (!_hasSqlSupport) {
182                 object value = connection.GetDataSourcePropertyValue(OleDbPropertySetGuid.DataSourceInfo, ODB.DBPROP_SQLSUPPORT);
183                 if (value is Int32) { // not OleDbPropertyStatus
184                     sqlSupport = (int) value;
185                 }
186                 _sqlSupport = sqlSupport;
187                 _hasSqlSupport = true;
188             }
189             return sqlSupport;
190         }
191
192         internal bool GetSupportIRow(OleDbConnection connection, OleDbCommand command) {
193             bool supportIRow = _supportIRow;
194             if (!_hasSupportIRow) {
195                 object value = command.GetPropertyValue(OleDbPropertySetGuid.Rowset, ODB.DBPROP_IRow);
196
197                 // SQLOLEDB always returns VARIANT_FALSE for DBPROP_IROW, so base the answer on existance
198                 supportIRow = !(value is OleDbPropertyStatus);
199                 _supportIRow = supportIRow;
200                 _hasSupportIRow = true;
201             }
202             return supportIRow;
203         }
204
205         internal bool GetSupportMultipleResults(OleDbConnection connection) {
206             bool supportMultipleResults = _supportMultipleResults;
207             if (!_hasSupportMultipleResults) {
208                 object value = connection.GetDataSourcePropertyValue(OleDbPropertySetGuid.DataSourceInfo, ODB.DBPROP_MULTIPLERESULTS);
209                 if (value is Int32) {// not OleDbPropertyStatus
210                     supportMultipleResults = (ODB.DBPROPVAL_MR_NOTSUPPORTED != (int) value);
211                 }
212                 _supportMultipleResults = supportMultipleResults;
213                 _hasSupportMultipleResults = true;
214             }
215             return supportMultipleResults;
216         }
217
218         static private int UdlPoolSize { // MDAC 69925
219             // SxS: UdpPoolSize reads registry value to get the pool size
220             [ResourceExposure(ResourceScope.None)]
221             [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
222             get {
223                 int poolsize = UDL._PoolSize;
224                 if (!UDL._PoolSizeInit) {
225                     object value = ADP.LocalMachineRegistryValue(UDL.Location, UDL.Pooling);
226                     if (value is Int32) {
227                         poolsize = (int) value;
228                         poolsize = ((0 < poolsize) ? poolsize : 0);
229                         UDL._PoolSize = poolsize;
230                     }
231                     UDL._PoolSizeInit = true;
232                 }
233                 return poolsize;
234             }
235         }
236
237         [ResourceExposure(ResourceScope.Machine)]
238         [ResourceConsumption(ResourceScope.Machine)]
239         static private string LoadStringFromStorage(string udlfilename) {
240             string udlConnectionString = null;
241             Dictionary<string,string> udlcache = UDL._Pool;
242
243             if ((null == udlcache) || !udlcache.TryGetValue(udlfilename, out udlConnectionString)) {
244                 udlConnectionString = LoadStringFromFileStorage(udlfilename);
245                 if (null != udlConnectionString) {
246                     Debug.Assert(!ADP.IsEmpty(udlfilename), "empty filename didn't fail");
247
248                     if (0 < UdlPoolSize) {
249                         Debug.Assert(udlfilename == ADP.GetFullPath(udlfilename), "only cache full path filenames"); // MDAC 82833
250
251                         if (null == udlcache) {
252                             udlcache = new Dictionary<string,string>();
253                             udlcache[udlfilename] = udlConnectionString;
254
255                             lock(UDL._PoolLock) {
256                                 if (null != UDL._Pool) {
257                                     udlcache = UDL._Pool;
258                                 }
259                                 else {
260                                     UDL._Pool = udlcache;
261                                     udlcache = null;
262                                 }
263                             }
264                         }
265                         if (null != udlcache) {
266                             lock(udlcache) {
267                                 udlcache[udlfilename] = udlConnectionString;
268                             }
269                         }
270                     }
271                 }
272             }
273             return udlConnectionString;
274         }
275
276         [ResourceExposure(ResourceScope.Machine)]
277         [ResourceConsumption(ResourceScope.Machine)]
278         static private string LoadStringFromFileStorage(string udlfilename) {
279             // Microsoft Data Link File Format
280             // The first two lines of a .udl file must have exactly the following contents in order to work properly:
281             //  [oledb]
282             //  ; Everything after this line is an OLE DB initstring
283             //
284             string connectionString = null;
285             Exception failure = null;
286             try {
287                 int hdrlength = ADP.CharSize*UDL.Header.Length;
288                 using(FileStream fstream = new FileStream(udlfilename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
289                     long length = fstream.Length;
290                     if (length < hdrlength || (0 != length%ADP.CharSize)) {
291                         failure = ADP.InvalidUDL();
292                     }
293                     else {
294                         byte[] bytes = new Byte[hdrlength];
295                         int count = fstream.Read(bytes, 0, bytes.Length);
296                         if (count < hdrlength) {
297                             failure = ADP.InvalidUDL();
298                         }
299                         else if (System.Text.Encoding.Unicode.GetString(bytes, 0, hdrlength) != UDL.Header) {
300                             failure = ADP.InvalidUDL();
301                         }
302                         else { // please verify header before allocating memory block for connection string
303                             bytes = new Byte[length - hdrlength];
304                             count = fstream.Read(bytes, 0, bytes.Length);
305                             connectionString = System.Text.Encoding.Unicode.GetString(bytes, 0, count);
306                         }
307                     }
308                 }
309             }
310             catch(Exception e) {
311                 // 
312                 if (!ADP.IsCatchableExceptionType(e)) {
313                     throw;
314                 }
315
316                 throw ADP.UdlFileError(e);
317             }
318             if (null != failure) {
319                 throw failure;
320             }
321             return connectionString.Trim();
322         }
323
324         [ResourceExposure(ResourceScope.None)] // reads OleDbServices value for the provider
325         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
326         private string ValidateConnectionString(string connectionString) {
327             if (ConvertValueToBoolean(KEY.Asynchronous_Processing, false)) {
328                 throw ODB.AsynchronousNotSupported();
329             }
330
331             int connectTimeout = ConvertValueToInt32(KEY.Connect_Timeout, 0);
332             if (connectTimeout < 0) {
333                 throw ADP.InvalidConnectTimeoutValue();
334             }
335
336             string progid = ConvertValueToString(KEY.Data_Provider, null); // MDAC 71923
337             if (null != progid) {
338                 progid = progid.Trim();
339                 if (0 < progid.Length) { // don't fail on empty 'Data Provider' value
340                     ValidateProvider(progid);
341                 }
342             }
343             progid = ConvertValueToString(KEY.RemoteProvider, null); // MDAC 71923
344             if (null != progid) {
345                 progid = progid.Trim();
346                 if (0 < progid.Length) { // don't fail on empty 'Data Provider' value
347                     ValidateProvider(progid);
348                 }
349             }
350             progid = ConvertValueToString(KEY.Provider, ADP.StrEmpty).Trim();
351             ValidateProvider(progid); // will fail on empty 'Provider' value
352
353             // SQLBU VSTS 59322: initialize to default
354             // If the value is not provided in connection string and OleDbServices registry key has not been set by the provider,
355             // the default for the provider is -1 (all services are ON).
356             // our default is -13, we turn off ODB.DBPROPVAL_OS_AGR_AFTERSESSION and ODB.DBPROPVAL_OS_CLIENTCURSOR flags
357             _oledbServices = DbConnectionStringDefaults.OleDbServices;
358
359             bool hasOleDBServices = (base.ContainsKey(KEY.Ole_DB_Services) && !ADP.IsEmpty((string)base[KEY.Ole_DB_Services]));
360             if (!hasOleDBServices) { // don't touch registry if they have OLE DB Services
361                 string classid = (string) ADP.ClassesRootRegistryValue(progid + "\\CLSID", String.Empty);
362                 if ((null != classid) && (0 < classid.Length)) {
363                     // CLSID detection of 'Microsoft OLE DB Provider for ODBC Drivers'
364                     Guid classidProvider = new Guid(classid);
365                     if (ODB.CLSID_MSDASQL == classidProvider) {
366                         throw ODB.MSDASQLNotSupported();
367                     }
368                     object tmp = ADP.ClassesRootRegistryValue("CLSID\\{" + classidProvider.ToString("D", CultureInfo.InvariantCulture) + "}", ODB.OLEDB_SERVICES);
369                     if (null != tmp) {
370
371                         // @devnote: some providers like MSDataShape don't have the OLEDB_SERVICES value
372                         // the MSDataShape provider doesn't support the 'Ole Db Services' keyword
373                         // hence, if the value doesn't exist - don't prepend to string
374                         try {
375                             _oledbServices = (int)tmp;
376                         }
377                         catch(InvalidCastException e) {
378                             ADP.TraceExceptionWithoutRethrow(e);
379                         }
380                         _oledbServices &= ~(ODB.DBPROPVAL_OS_AGR_AFTERSESSION | ODB.DBPROPVAL_OS_CLIENTCURSOR); // NT 347436, MDAC 58606
381
382                         StringBuilder builder = new StringBuilder();
383                         builder.Append(KEY.Ole_DB_Services);
384                         builder.Append("=");
385                         builder.Append(_oledbServices.ToString(CultureInfo.InvariantCulture));
386                         builder.Append(";");
387                         builder.Append(connectionString);
388                         connectionString = builder.ToString();
389                     }
390                 }
391             }
392             else {
393                 // SQLBU VSTS 59322: parse the Ole Db Services value from connection string
394                 _oledbServices = ConvertValueToInt32(KEY.Ole_DB_Services, DbConnectionStringDefaults.OleDbServices);
395             }
396
397             return connectionString;
398         }
399
400         internal static bool IsMSDASQL(string progid) {
401             return (("msdasql" == progid) || progid.StartsWith("msdasql.", StringComparison.Ordinal) || ("microsoft ole db provider for odbc drivers" == progid));
402         }
403
404         static private void ValidateProvider(string progid) {
405             if (ADP.IsEmpty(progid)) {
406                 throw ODB.NoProviderSpecified();
407             }
408             if (ODB.MaxProgIdLength <= progid.Length) { // MDAC 63151
409                 throw ODB.InvalidProviderSpecified();
410             }
411             progid = progid.ToLower(CultureInfo.InvariantCulture);
412             if (IsMSDASQL(progid)) {
413                 // fail msdasql even if not on the machine.
414                 throw ODB.MSDASQLNotSupported();
415             }
416         }
417         
418         static internal void ReleaseObjectPool() {
419             UDL._PoolSizeInit = false;
420             UDL._Pool = null;
421         }
422     }
423 }
424