Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / EntityClient / DbConnectionOptions.cs
1 //---------------------------------------------------------------------
2 // <copyright file="DbConnectionOptions.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner  Microsoft
7 // @backupOwner  Microsoft
8 //---------------------------------------------------------------------
9
10 namespace System.Data.EntityClient
11 {
12     using System.Collections;
13     using System.Diagnostics;
14     using System.Runtime.Versioning;
15     using System.Text;
16     using System.Text.RegularExpressions;
17
18     /// <summary>
19     /// Copied from System.Data.dll
20     /// </summary>
21     internal class DbConnectionOptions
22     {
23         // instances of this class are intended to be immutable, i.e readonly
24         // used by pooling classes so it is much easier to verify correctness
25         // when not worried about the class being modified during execution
26
27 #if DEBUG
28         private const string ConnectionStringPattern =                  // may not contain embedded null except trailing last value
29             "([\\s;]*"                                                  // leading whitespace and extra semicolons
30             + "(?![\\s;])"                                              // key does not start with space or semicolon
31             + "(?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)"  // allow any visible character for keyname except '=' which must quoted as '=='
32             + "\\s*=(?!=)\\s*"                                          // the equal sign divides the key and value parts
33             + "(?<value>"
34             + "(\"([^\"\u0000]|\"\")*\")"                              // double quoted string, " must be quoted as ""
35             + "|"
36             + "('([^'\u0000]|'')*')"                                   // single quoted string, ' must be quoted as ''
37             + "|"
38             + "((?![\"'\\s])"                                          // unquoted value must not start with " or ' or space, would also like = but too late to change
39             + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*"                  // control characters must be quoted
40             + "(?<![\"']))"                                            // unquoted value must not stop with " or '
41             + ")(\\s*)(;|\u0000|$)"                                     // whitespace after value up to semicolon or end-of-line
42             + ")*"                                                      // repeat the key-value pair
43             + "[\\s;\u0000]*"                                           // traling whitespace/semicolons and embedded nulls (DataSourceLocator)
44         ;
45
46         private static readonly Regex ConnectionStringRegex = new Regex(ConnectionStringPattern, RegexOptions.ExplicitCapture | RegexOptions.Compiled);
47 #endif
48         internal const string DataDirectory = "|datadirectory|";
49
50 #if DEBUG 
51         private const string ConnectionStringValidKeyPattern = "^(?![;\\s])[^\\p{Cc}]+(?<!\\s)$"; // key not allowed to start with semi-colon or space or contain non-visible characters or end with space
52         private const string ConnectionStringValidValuePattern = "^[^\u0000]*$";                    // value not allowed to contain embedded null   
53         private static readonly Regex ConnectionStringValidKeyRegex = new Regex(ConnectionStringValidKeyPattern, RegexOptions.Compiled);
54         private static readonly Regex ConnectionStringValidValueRegex = new Regex(ConnectionStringValidValuePattern, RegexOptions.Compiled);
55 #endif
56
57         private readonly string _usersConnectionString;
58         private readonly Hashtable _parsetable;
59         internal readonly NameValuePair KeyChain;
60
61         // synonyms hashtable is meant to be read-only translation of parsed string
62         // keywords/synonyms to a known keyword string
63         internal DbConnectionOptions(string connectionString, Hashtable synonyms)
64         {
65             _parsetable = new Hashtable();
66             _usersConnectionString = ((null != connectionString) ? connectionString : "");
67
68             // first pass on parsing, initial syntax check
69             if (0 < _usersConnectionString.Length)
70             {
71                 KeyChain = ParseInternal(_parsetable, _usersConnectionString, synonyms);
72             }
73         }
74
75         internal string UsersConnectionString
76         {
77             get
78             {
79                 return _usersConnectionString ?? string.Empty;
80             }
81         }
82
83         internal bool IsEmpty
84         {
85             get { return (null == KeyChain); }
86         }
87
88         internal Hashtable Parsetable
89         {
90             get { return _parsetable; }
91         }
92
93         internal string this[string keyword]
94         {
95             get { return (string)_parsetable[keyword]; }
96         }
97
98         // SxS notes:
99         // * this method queries "DataDirectory" value from the current AppDomain.
100         //   This string is used for to replace "!DataDirectory!" values in the connection string, it is not considered as an "exposed resource".
101         // * This method uses GetFullPath to validate that root path is valid, the result is not exposed out.
102         [ResourceExposure(ResourceScope.None)]
103         [ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)]
104         internal static string ExpandDataDirectory(string keyword, string value)
105         {
106             string fullPath = null;
107             if ((null != value) && value.StartsWith(DataDirectory, StringComparison.OrdinalIgnoreCase))
108             {
109                 // find the replacement path
110                 object rootFolderObject = AppDomain.CurrentDomain.GetData("DataDirectory");
111                 string rootFolderPath = (rootFolderObject as string);
112                 if ((null != rootFolderObject) && (null == rootFolderPath))
113                 {
114                     throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.ADP_InvalidDataDirectory);
115                 }
116                 else if (rootFolderPath == string.Empty)
117                 {
118                     rootFolderPath = AppDomain.CurrentDomain.BaseDirectory;
119                 }
120                 if (null == rootFolderPath)
121                 {
122                     rootFolderPath = "";                    
123                 }
124
125                 // We don't know if rootFolderpath ends with '\', and we don't know if the given name starts with onw
126                 int fileNamePosition = DataDirectory.Length;    // filename starts right after the '|datadirectory|' keyword
127                 bool rootFolderEndsWith = (0 < rootFolderPath.Length) && rootFolderPath[rootFolderPath.Length - 1] == '\\';
128                 bool fileNameStartsWith = (fileNamePosition < value.Length) && value[fileNamePosition] == '\\';
129
130                 // replace |datadirectory| with root folder path
131                 if (!rootFolderEndsWith && !fileNameStartsWith)
132                 {
133                     // need to insert '\'
134                     fullPath = rootFolderPath + '\\' + value.Substring(fileNamePosition);
135                 }
136                 else if (rootFolderEndsWith && fileNameStartsWith)
137                 {
138                     // need to strip one out
139                     fullPath = rootFolderPath + value.Substring(fileNamePosition + 1);
140                 }
141                 else
142                 {
143                     // simply concatenate the strings
144                     fullPath = rootFolderPath + value.Substring(fileNamePosition);
145                 }
146
147                 // verify root folder path is a real path without unexpected "..\"
148                 if (!EntityUtil.GetFullPath(fullPath).StartsWith(rootFolderPath, StringComparison.Ordinal))
149                 {
150                     throw EntityUtil.InvalidConnectionOptionValue(keyword);
151                 }
152             }
153             return fullPath;
154         }
155
156         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
157         static private string GetKeyName(StringBuilder buffer)
158         {
159             int count = buffer.Length;
160             while ((0 < count) && Char.IsWhiteSpace(buffer[count - 1]))
161             {
162                 count--; // trailing whitespace
163             }
164             return buffer.ToString(0, count).ToLowerInvariant();
165         }
166
167         static private string GetKeyValue(StringBuilder buffer, bool trimWhitespace)
168         {
169             int count = buffer.Length;
170             int index = 0;
171             if (trimWhitespace)
172             {
173                 while ((index < count) && Char.IsWhiteSpace(buffer[index]))
174                 {
175                     index++; // leading whitespace
176                 }
177                 while ((0 < count) && Char.IsWhiteSpace(buffer[count - 1]))
178                 {
179                     count--; // trailing whitespace
180                 }
181             }
182             return buffer.ToString(index, count - index);
183         }
184
185         // transistion states used for parsing
186         private enum ParserState
187         {
188             NothingYet = 1,   //start point
189             Key,
190             KeyEqual,
191             KeyEnd,
192             UnquotedValue,
193             DoubleQuoteValue,
194             DoubleQuoteValueQuote,
195             SingleQuoteValue,
196             SingleQuoteValueQuote,
197             QuotedValueEnd,
198             NullTermination,
199         };
200         
201         static private int GetKeyValuePair(string connectionString, int currentPosition, StringBuilder buffer, out string keyname, out string keyvalue)
202         {
203             int startposition = currentPosition;
204
205             buffer.Length = 0;
206             keyname = null;
207             keyvalue = null;
208
209             char currentChar = '\0';
210
211             ParserState parserState = ParserState.NothingYet;
212             int length = connectionString.Length;
213             for (; currentPosition < length; ++currentPosition)
214             {
215                 currentChar = connectionString[currentPosition];
216
217                 switch (parserState)
218                 {
219                     case ParserState.NothingYet: // [\\s;]*
220                         if ((';' == currentChar) || Char.IsWhiteSpace(currentChar))
221                         {
222                             continue;
223                         }
224                         if ('\0' == currentChar) { parserState = ParserState.NullTermination; continue; } // MDAC 83540
225                         if (Char.IsControl(currentChar)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
226                         startposition = currentPosition;
227                         if ('=' != currentChar)
228                         { // MDAC 86902
229                             parserState = ParserState.Key;
230                             break;
231                         }
232                         else
233                         {
234                             parserState = ParserState.KeyEqual;
235                             continue;
236                         }
237
238                     case ParserState.Key: // (?<key>([^=\\s\\p{Cc}]|\\s+[^=\\s\\p{Cc}]|\\s+==|==)+)
239                         if ('=' == currentChar) { parserState = ParserState.KeyEqual; continue; }
240                         if (Char.IsWhiteSpace(currentChar)) { break; }
241                         if (Char.IsControl(currentChar)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
242                         break;
243
244                     case ParserState.KeyEqual: // \\s*=(?!=)\\s*
245                         if ('=' == currentChar) { parserState = ParserState.Key; break; }
246                         keyname = GetKeyName(buffer);
247                         if (string.IsNullOrEmpty(keyname)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
248                         buffer.Length = 0;
249                         parserState = ParserState.KeyEnd;
250                         goto case ParserState.KeyEnd;
251
252                     case ParserState.KeyEnd:
253                         if (Char.IsWhiteSpace(currentChar)) { continue; }
254                         if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValue; continue; }
255                         if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValue; continue; }
256
257                         if (';' == currentChar) { goto ParserExit; }
258                         if ('\0' == currentChar) { goto ParserExit; }
259                         if (Char.IsControl(currentChar)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
260                         parserState = ParserState.UnquotedValue;
261                         break;
262
263                     case ParserState.UnquotedValue: // "((?![\"'\\s])" + "([^;\\s\\p{Cc}]|\\s+[^;\\s\\p{Cc}])*" + "(?<![\"']))"
264                         if (Char.IsWhiteSpace(currentChar)) { break; }
265                         if (Char.IsControl(currentChar) || ';' == currentChar) { goto ParserExit; }
266                         break;
267
268                     case ParserState.DoubleQuoteValue: // "(\"([^\"\u0000]|\"\")*\")"
269                         if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValueQuote; continue; }
270                         if ('\0' == currentChar) { throw EntityUtil.ConnectionStringSyntax(startposition); }
271                         break;
272
273                     case ParserState.DoubleQuoteValueQuote:
274                         if ('"' == currentChar) { parserState = ParserState.DoubleQuoteValue; break; }
275                         keyvalue = GetKeyValue(buffer, false);
276                         parserState = ParserState.QuotedValueEnd;
277                         goto case ParserState.QuotedValueEnd;
278
279                     case ParserState.SingleQuoteValue: // "('([^'\u0000]|'')*')"
280                         if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValueQuote; continue; }
281                         if ('\0' == currentChar) { throw EntityUtil.ConnectionStringSyntax(startposition); }
282                         break;
283
284                     case ParserState.SingleQuoteValueQuote:
285                         if ('\'' == currentChar) { parserState = ParserState.SingleQuoteValue; break; }
286                         keyvalue = GetKeyValue(buffer, false);
287                         parserState = ParserState.QuotedValueEnd;
288                         goto case ParserState.QuotedValueEnd;
289
290                     case ParserState.QuotedValueEnd:
291                         if (Char.IsWhiteSpace(currentChar)) { continue; }
292                         if (';' == currentChar) { goto ParserExit; }
293                         if ('\0' == currentChar) { parserState = ParserState.NullTermination; continue; } // MDAC 83540
294                         throw EntityUtil.ConnectionStringSyntax(startposition);  // unbalanced single quote
295
296                     case ParserState.NullTermination: // [\\s;\u0000]*
297                         if ('\0' == currentChar) { continue; }
298                         if (Char.IsWhiteSpace(currentChar)) { continue; } // MDAC 83540
299                         throw EntityUtil.ConnectionStringSyntax(currentPosition);
300
301                     default:
302                         throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidParserState1);
303                 }
304                 buffer.Append(currentChar);
305             }
306         ParserExit:
307             switch (parserState)
308             {
309                 case ParserState.Key:
310                 case ParserState.DoubleQuoteValue:
311                 case ParserState.SingleQuoteValue:
312                     // keyword not found/unbalanced double/single quote
313                     throw EntityUtil.ConnectionStringSyntax(startposition);
314
315                 case ParserState.KeyEqual:
316                     // equal sign at end of line
317                     keyname = GetKeyName(buffer);
318                     if (string.IsNullOrEmpty(keyname)) { throw EntityUtil.ConnectionStringSyntax(startposition); }
319                     break;
320
321                 case ParserState.UnquotedValue:
322                     // unquoted value at end of line
323                     keyvalue = GetKeyValue(buffer, true);
324
325                     char tmpChar = keyvalue[keyvalue.Length - 1];
326                     if (('\'' == tmpChar) || ('"' == tmpChar))
327                     {
328                         throw EntityUtil.ConnectionStringSyntax(startposition);    // unquoted value must not end in quote
329                     }
330                     break;
331
332                 case ParserState.DoubleQuoteValueQuote:
333                 case ParserState.SingleQuoteValueQuote:
334                 case ParserState.QuotedValueEnd:
335                     // quoted value at end of line
336                     keyvalue = GetKeyValue(buffer, false);
337                     break;
338
339                 case ParserState.NothingYet:
340                 case ParserState.KeyEnd:
341                 case ParserState.NullTermination:
342                     // do nothing
343                     break;
344
345                 default:
346                     throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.InvalidParserState2);
347             }
348             if ((';' == currentChar) && (currentPosition < connectionString.Length))
349             {
350                 currentPosition++;
351             }
352             return currentPosition;
353         }
354
355 #if DEBUG
356         static private bool IsValueValidInternal(string keyvalue)
357         {
358             if (null != keyvalue)
359             {
360
361                 bool compValue = ConnectionStringValidValueRegex.IsMatch(keyvalue);
362                 Debug.Assert((-1 == keyvalue.IndexOf('\u0000')) == compValue, "IsValueValid mismatch with regex");
363                 return (-1 == keyvalue.IndexOf('\u0000'));
364             }
365             return true;
366         }
367 #endif
368
369         static private bool IsKeyNameValid(string keyname)
370         {
371             if (null != keyname)
372             {
373 #if DEBUG
374                 bool compValue = ConnectionStringValidKeyRegex.IsMatch(keyname);
375                 Debug.Assert(((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000'))) == compValue, "IsValueValid mismatch with regex");
376 #endif
377                 return ((0 < keyname.Length) && (';' != keyname[0]) && !Char.IsWhiteSpace(keyname[0]) && (-1 == keyname.IndexOf('\u0000')));
378             }
379             return false;
380         }
381
382 #if DEBUG
383         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
384         private static Hashtable SplitConnectionString(string connectionString, Hashtable synonyms)
385         {
386             Hashtable parsetable = new Hashtable();
387             Regex parser = ConnectionStringRegex;
388
389             const int KeyIndex = 1, ValueIndex = 2;
390             Debug.Assert(KeyIndex == parser.GroupNumberFromName("key"), "wrong key index");
391             Debug.Assert(ValueIndex == parser.GroupNumberFromName("value"), "wrong value index");
392
393             if (null != connectionString)
394             {
395                 Match match = parser.Match(connectionString);
396                 if (!match.Success || (match.Length != connectionString.Length))
397                 {
398                     throw EntityUtil.ConnectionStringSyntax(match.Length);
399                 }
400                 int indexValue = 0;
401                 CaptureCollection keyvalues = match.Groups[ValueIndex].Captures;
402                 foreach (Capture keypair in match.Groups[KeyIndex].Captures)
403                 {
404                     string keyname = keypair.Value.Replace("==", "=").ToLowerInvariant();
405                     string keyvalue = keyvalues[indexValue++].Value;
406                     if (0 < keyvalue.Length)
407                     {
408                         switch (keyvalue[0])
409                         {
410                             case '\"':
411                                 keyvalue = keyvalue.Substring(1, keyvalue.Length - 2).Replace("\"\"", "\"");
412                                 break;
413                             case '\'':
414                                 keyvalue = keyvalue.Substring(1, keyvalue.Length - 2).Replace("\'\'", "\'");
415                                 break;
416                             default:
417                                 break;
418                         }
419                     }
420                     else
421                     {
422                         keyvalue = null;
423                     }
424
425                     string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
426                     if (!IsKeyNameValid(realkeyname))
427                     {
428                         throw EntityUtil.ADP_KeywordNotSupported(keyname);
429                     }
430                     parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
431                 }
432             }
433             return parsetable;
434         }
435
436         private static void ParseComparision(Hashtable parsetable, string connectionString, Hashtable synonyms, Exception e)
437         {
438             try
439             {
440                 Hashtable parsedvalues = SplitConnectionString(connectionString, synonyms);
441                 foreach (DictionaryEntry entry in parsedvalues)
442                 {
443                     string keyname = (string)entry.Key;
444                     string value1 = (string)entry.Value;
445                     string value2 = (string)parsetable[keyname];
446                     Debug.Assert(parsetable.Contains(keyname), "ParseInternal code vs. regex mismatch keyname <" + keyname + ">");
447                     Debug.Assert(value1 == value2, "ParseInternal code vs. regex mismatch keyvalue <" + value1 + "> <" + value2 + ">");
448                 }
449
450             }
451             catch (ArgumentException f)
452             {
453                 if (null != e)
454                 {
455                     string msg1 = e.Message;
456                     string msg2 = f.Message;
457                     if (msg1.StartsWith("Keyword not supported:", StringComparison.Ordinal) && msg2.StartsWith("Format of the initialization string", StringComparison.Ordinal))
458                     {
459                     }
460                     else
461                     {
462                         // Does not always hold.
463                        Debug.Assert(msg1 == msg2, "ParseInternal code vs regex message mismatch: <" + msg1 + "> <" + msg2 + ">");
464                     }
465                 }
466                 else
467                 {
468                     Debug.Assert(false, "ParseInternal code vs regex throw mismatch " + f.Message);
469                 }
470                 e = null;
471             }
472             if (null != e)
473             {
474                 Debug.Assert(false, "ParseInternal code threw exception vs regex mismatch");
475             }
476         }
477 #endif
478         private static NameValuePair ParseInternal(Hashtable parsetable, string connectionString, Hashtable synonyms)
479         {
480             Debug.Assert(null != connectionString, "null connectionstring");
481             StringBuilder buffer = new StringBuilder();
482             NameValuePair localKeychain = null, keychain = null;
483 #if DEBUG
484             try
485             {
486 #endif
487                 int nextStartPosition = 0;
488                 int endPosition = connectionString.Length;
489                 while (nextStartPosition < endPosition)
490                 {
491                     int startPosition = nextStartPosition;
492
493                     string keyname, keyvalue;
494                     nextStartPosition = GetKeyValuePair(connectionString, startPosition, buffer, out keyname, out keyvalue);
495                     if (string.IsNullOrEmpty(keyname))
496                     {
497                         // if (nextStartPosition != endPosition) { throw; }
498                         break;
499                     }
500
501 #if DEBUG
502                     Debug.Assert(IsKeyNameValid(keyname), "ParseFailure, invalid keyname");
503                     Debug.Assert(IsValueValidInternal(keyvalue), "parse failure, invalid keyvalue");
504 #endif
505                     string realkeyname = ((null != synonyms) ? (string)synonyms[keyname] : keyname);
506                     if (!IsKeyNameValid(realkeyname))
507                     {
508                         throw EntityUtil.ADP_KeywordNotSupported(keyname);
509                     }
510                     parsetable[realkeyname] = keyvalue; // last key-value pair wins (or first)
511
512                     if (null != localKeychain)
513                     {
514                         localKeychain = localKeychain.Next = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
515                     }
516                     else 
517                     { // first time only - don't contain modified chain from UDL file
518                         keychain = localKeychain = new NameValuePair(realkeyname, keyvalue, nextStartPosition - startPosition);
519                     }
520                 }
521 #if DEBUG
522             }
523             catch (ArgumentException e)
524             {
525                 ParseComparision(parsetable, connectionString, synonyms, e);
526                 throw;
527             }
528             ParseComparision(parsetable, connectionString, synonyms, null);
529 #endif
530             return keychain;
531         } 
532     }
533 }