< Summary

Class:Azure.Storage.StorageConnectionString
Assembly:Azure.Storage.Files.DataLake
File(s):C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Common\src\Shared\StorageConnectionString.cs
Covered lines:0
Uncovered lines:407
Coverable lines:407
Total lines:1034
Line coverage:0% (0 of 407)
Covered branches:0
Total branches:146
Branch coverage:0% (0 of 146)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
get_UseV1MD5()-0%100%
.cctor()-0%0%
.ctor(...)-0%100%
get_DevelopmentStorageAccount()-0%0%
get_IsDevStoreAccount()-0%100%
get_EndpointSuffix()-0%100%
get_Settings()-0%100%
get_DefaultEndpoints()-0%100%
get_BlobEndpoint()-0%100%
get_QueueEndpoint()-0%100%
get_TableEndpoint()-0%100%
get_FileEndpoint()-0%100%
get_BlobStorageUri()-0%100%
get_QueueStorageUri()-0%100%
get_TableStorageUri()-0%100%
get_FileStorageUri()-0%100%
get_Credentials()-0%100%
Parse(...)-0%0%
TryParse(...)-0%0%
GetDevelopmentStorageAccount(...)-0%0%
ParseCore(...)-0%0%
ParseStringIntoSettings(...)-0%0%
Setting(...)-0%0%
Setting(...)-0%100%
IsValidBase64String(...)-0%100%
IsValidUri(...)-0%100%
IsValidDomain(...)-0%100%
AllRequired(...)-0%0%
Optional(...)-0%0%
AtLeastOne(...)-0%0%
None(...)-0%0%
MatchesAll(...)-0%0%
MatchesOne(...)-0%0%
MatchesExactly(...)-0%0%
MatchesSpecification(...)-0%0%
GetCredentials(...)-0%0%
ConstructBlobEndpoint(...)-0%0%
ConstructBlobEndpoint(...)-0%0%
ConstructFileEndpoint(...)-0%0%
ConstructFileEndpoint(...)-0%0%
ConstructQueueEndpoint(...)-0%0%
ConstructQueueEndpoint(...)-0%0%
ConstructTableEndpoint(...)-0%0%
ConstructTableEndpoint(...)-0%0%
ConstructUris(...)-0%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\storage\Azure.Storage.Common\src\Shared\StorageConnectionString.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Diagnostics.CodeAnalysis;
 7using System.Globalization;
 8using System.Linq;
 9using AccountSetting = System.Collections.Generic.KeyValuePair<string, System.Func<string, bool>>;
 10using ConnectionStringFilter = System.Func<System.Collections.Generic.IDictionary<string, string>, System.Collections.Ge
 11
 12#pragma warning disable SA1402  // File may only contain a single type
 13
 14namespace Azure.Storage
 15{
 16    internal class StorageConnectionString
 17    {
 18        /// <summary>
 19        /// Gets or sets a value indicating whether the FISMA MD5 setting will be used.
 20        /// </summary>
 21        /// <value><c>false</c> to use the FISMA MD5 setting; <c>true</c> to use the .NET default implementation.</value
 022        internal static bool UseV1MD5 => true;
 23
 24        /// <summary>
 25        /// Validator for the UseDevelopmentStorage setting. Must be "true".
 26        /// </summary>
 027        private static readonly AccountSetting s_useDevelopmentStorageSetting = Setting(Constants.ConnectionStrings.UseD
 28
 29        /// <summary>
 30        /// Validator for the DevelopmentStorageProxyUri setting. Must be a valid Uri.
 31        /// </summary>
 032        private static readonly AccountSetting s_developmentStorageProxyUriSetting = Setting(Constants.ConnectionStrings
 33
 34        /// <summary>
 35        /// Validator for the DefaultEndpointsProtocol setting. Must be either "http" or "https".
 36        /// </summary>
 037        private static readonly AccountSetting s_defaultEndpointsProtocolSetting = Setting(Constants.ConnectionStrings.D
 38
 39        /// <summary>
 40        /// Validator for the AccountName setting. No restrictions.
 41        /// </summary>
 042        private static readonly AccountSetting s_accountNameSetting = Setting(Constants.ConnectionStrings.AccountNameSet
 43
 44        /// <summary>
 45        /// Validator for the AccountKey setting. No restrictions.
 46        /// </summary>
 047        private static readonly AccountSetting s_accountKeyNameSetting = Setting(Constants.ConnectionStrings.AccountKeyN
 48
 49        /// <summary>
 50        /// Validator for the AccountKey setting. Must be a valid base64 string.
 51        /// </summary>
 052        private static readonly AccountSetting s_accountKeySetting = Setting(Constants.ConnectionStrings.AccountKeySetti
 53
 54        /// <summary>
 55        /// Validator for the BlobEndpoint setting. Must be a valid Uri.
 56        /// </summary>
 057        private static readonly AccountSetting s_blobEndpointSetting = Setting(Constants.ConnectionStrings.BlobEndpointS
 58
 59        /// <summary>
 60        /// Validator for the QueueEndpoint setting. Must be a valid Uri.
 61        /// </summary>
 062        private static readonly AccountSetting s_queueEndpointSetting = Setting(Constants.ConnectionStrings.QueueEndpoin
 63
 64        /// <summary>
 65        /// Validator for the FileEndpoint setting. Must be a valid Uri.
 66        /// </summary>
 067        private static readonly AccountSetting s_fileEndpointSetting = Setting(Constants.ConnectionStrings.FileEndpointS
 68
 69        /// <summary>
 70        /// Validator for the TableEndpoint setting. Must be a valid Uri.
 71        /// </summary>
 072        private static readonly AccountSetting s_tableEndpointSetting = Setting(Constants.ConnectionStrings.TableEndpoin
 73
 74        /// <summary>
 75        /// Validator for the BlobSecondaryEndpoint setting. Must be a valid Uri.
 76        /// </summary>
 077        private static readonly AccountSetting s_blobSecondaryEndpointSetting = Setting(Constants.ConnectionStrings.Blob
 78
 79        /// <summary>
 80        /// Validator for the QueueSecondaryEndpoint setting. Must be a valid Uri.
 81        /// </summary>
 082        private static readonly AccountSetting s_queueSecondaryEndpointSetting = Setting(Constants.ConnectionStrings.Que
 83
 84        /// <summary>
 85        /// Validator for the FileSecondaryEndpoint setting. Must be a valid Uri.
 86        /// </summary>
 087        private static readonly AccountSetting s_fileSecondaryEndpointSetting = Setting(Constants.ConnectionStrings.File
 88
 89        /// <summary>
 90        /// Validator for the TableSecondaryEndpoint setting. Must be a valid Uri.
 91        /// </summary>
 092        private static readonly AccountSetting s_tableSecondaryEndpointSetting = Setting(Constants.ConnectionStrings.Tab
 93
 94        /// <summary>
 95        /// Validator for the EndpointSuffix setting. Must be a valid Uri.
 96        /// </summary>
 097        private static readonly AccountSetting s_endpointSuffixSetting = Setting(Constants.ConnectionStrings.EndpointSuf
 98
 99        /// <summary>
 100        /// Validator for the SharedAccessSignature setting. No restrictions.
 101        /// </summary>
 0102        private static readonly AccountSetting s_sharedAccessSignatureSetting = Setting(Constants.ConnectionStrings.Shar
 103
 104        /// <summary>
 105        /// Singleton instance for the development storage account.
 106        /// </summary>
 107        private static StorageConnectionString s_devStoreAccount;
 108
 109        /// <summary>
 110        /// Initializes a new instance of the <see cref="StorageConnectionString"/> class using the specified
 111        /// account credentials and service endpoints.
 112        /// </summary>
 113        /// <param name="storageCredentials">A StorageCredentials object.</param>
 114        /// <param name="blobStorageUri">A <see cref="System.Uri"/> specifying the Blob service endpoint or endpoints.</
 115        /// <param name="queueStorageUri">A <see cref="System.Uri"/> specifying the Queue service endpoint or endpoints.
 116        /// <param name="tableStorageUri">A <see cref="System.Uri"/> specifying the Table service endpoint or endpoints.
 117        /// <param name="fileStorageUri">A <see cref="System.Uri"/> specifying the File service endpoint or endpoints.</
 0118        public StorageConnectionString(
 0119            object storageCredentials,
 0120            (Uri, Uri) blobStorageUri = default,
 0121            (Uri, Uri) queueStorageUri = default,
 0122            (Uri, Uri) tableStorageUri = default,
 0123            (Uri, Uri) fileStorageUri = default)
 124        {
 0125            Credentials = storageCredentials;
 0126            BlobStorageUri = blobStorageUri;
 0127            QueueStorageUri = queueStorageUri;
 0128            TableStorageUri = tableStorageUri;
 0129            FileStorageUri = fileStorageUri;
 0130            DefaultEndpoints = false;
 0131        }
 132
 133
 134        /// <summary>
 135        /// Gets a <see cref="StorageConnectionString"/> object that references the well-known development storage accou
 136        /// </summary>
 137        /// <value>A <see cref="StorageConnectionString"/> object representing the development storage account.</value>
 138        public static StorageConnectionString DevelopmentStorageAccount
 139        {
 140            get
 141            {
 0142                if (s_devStoreAccount == null)
 143                {
 0144                    s_devStoreAccount = GetDevelopmentStorageAccount(null);
 145                }
 146
 0147                return s_devStoreAccount;
 148            }
 149        }
 150
 151        /// <summary>
 152        /// Indicates whether this account is a development storage account.
 153        /// </summary>
 0154        internal bool IsDevStoreAccount { get; set; }
 155
 156        /// <summary>
 157        /// The storage service hostname suffix set by the user, if any.
 158        /// </summary>
 0159        internal string EndpointSuffix { get; set; }
 160
 161        /// <summary>
 162        /// The connection string parsed into settings.
 163        /// </summary>
 0164        internal IDictionary<string, string> Settings { get; set; }
 165
 166        /// <summary>
 167        /// True if the user used a constructor that auto-generates endpoints.
 168        /// </summary>
 0169        internal bool DefaultEndpoints { get; set; }
 170
 171        /// <summary>
 172        /// Gets the primary endpoint for the Blob service, as configured for the storage account.
 173        /// </summary>
 174        /// <value>A <see cref="System.Uri"/> containing the primary Blob service endpoint.</value>
 0175        public Uri BlobEndpoint => BlobStorageUri.PrimaryUri;
 176
 177        /// <summary>
 178        /// Gets the primary endpoint for the Queue service, as configured for the storage account.
 179        /// </summary>
 180        /// <value>A <see cref="System.Uri"/> containing the primary Queue service endpoint.</value>
 0181        public Uri QueueEndpoint => QueueStorageUri.PrimaryUri;
 182
 183        /// <summary>
 184        /// Gets the primary endpoint for the Table service, as configured for the storage account.
 185        /// </summary>
 186        /// <value>A <see cref="System.Uri"/> containing the primary Table service endpoint.</value>
 0187        public Uri TableEndpoint => TableStorageUri.PrimaryUri;
 188
 189        /// <summary>
 190        /// Gets the primary endpoint for the File service, as configured for the storage account.
 191        /// </summary>
 192        /// <value>A <see cref="System.Uri"/> containing the primary File service endpoint.</value>
 0193        public Uri FileEndpoint => FileStorageUri.PrimaryUri;
 194
 195        /// <summary>
 196        /// Gets the endpoints for the Blob service at the primary and secondary location, as configured for the storage
 197        /// </summary>
 198        /// <value>A <see cref="System.Uri"/> containing the Blob service endpoints.</value>
 0199        public (Uri PrimaryUri, Uri SecondaryUri) BlobStorageUri { get; set; }
 200
 201        /// <summary>
 202        /// Gets the endpoints for the Queue service at the primary and secondary location, as configured for the storag
 203        /// </summary>
 204        /// <value>A <see cref="System.Uri"/> containing the Queue service endpoints.</value>
 0205        public (Uri PrimaryUri, Uri SecondaryUri) QueueStorageUri { get; set; }
 206
 207        /// <summary>
 208        /// Gets the endpoints for the Table service at the primary and secondary location, as configured for the storag
 209        /// </summary>
 210        /// <value>A <see cref="System.Uri"/> containing the Table service endpoints.</value>
 0211        public (Uri PrimaryUri, Uri SecondaryUri) TableStorageUri { get; set; }
 212
 213        /// <summary>
 214        /// Gets the endpoints for the File service at the primary and secondary location, as configured for the storage
 215        /// </summary>
 216        /// <value>A <see cref="System.Uri"/> containing the File service endpoints.</value>
 0217        public (Uri PrimaryUri, Uri SecondaryUri) FileStorageUri { get; set; }
 218
 219        /// <summary>
 220        /// Gets the credentials used to create this <see cref="StorageConnectionString"/> object.
 221        /// </summary>
 222        /// <value>A StorageCredentials object.</value>
 0223        public object Credentials { get; set; }
 224
 225        /// <summary>
 226        /// Private record of the account name for use in ToString(bool).
 227        /// </summary>
 228        internal string _accountName;
 229
 230        /// <summary>
 231        /// Parses a connection string and returns a <see cref="StorageConnectionString"/> created
 232        /// from the connection string.
 233        /// </summary>
 234        /// <param name="connectionString">A valid connection string.</param>
 235        /// <exception cref="ArgumentNullException">Thrown if <paramref name="connectionString"/> is null or empty.</exc
 236        /// <exception cref="FormatException">Thrown if <paramref name="connectionString"/> is not a valid connection st
 237        /// <exception cref="ArgumentException">Thrown if <paramref name="connectionString"/> cannot be parsed.</excepti
 238        /// <returns>A <see cref="StorageConnectionString"/> object constructed from the values provided in the connecti
 239        public static StorageConnectionString Parse(string connectionString)
 240        {
 241
 0242            if (string.IsNullOrEmpty(connectionString))
 243            {
 0244                throw Errors.ArgumentNull(nameof(connectionString));
 245            }
 246
 0247            if (ParseCore(connectionString, out StorageConnectionString ret, err => { throw Errors.InvalidFormat(err); }
 248            {
 0249                return ret;
 250            }
 251
 0252            throw Errors.ParsingConnectionStringFailed();
 253        }
 254
 255        /// <summary>
 256        /// Indicates whether a connection string can be parsed to return a <see cref="StorageConnectionString"/> object
 257        /// </summary>
 258        /// <param name="connectionString">The connection string to parse.</param>
 259        /// <param name="account">A <see cref="StorageConnectionString"/> object to hold the instance returned if
 260        /// the connection string can be parsed.</param>
 261        /// <returns><b>true</b> if the connection string was successfully parsed; otherwise, <b>false</b>.</returns>
 262        public static bool TryParse(string connectionString, out StorageConnectionString account)
 263        {
 0264            if (string.IsNullOrEmpty(connectionString))
 265            {
 0266                account = null;
 0267                return false;
 268            }
 269
 270            try
 271            {
 0272                return ParseCore(connectionString, out account, err => { });
 273            }
 0274            catch (Exception)
 275            {
 0276                account = null;
 0277                return false;
 278            }
 0279        }
 280
 281        ///// <summary>
 282        ///// Returns a shared access signature for the account.
 283        ///// </summary>
 284        ///// <param name="policy">A <see cref="SharedAccessAccountPolicy"/> object specifying the access policy for the
 285        ///// <returns>A shared access signature, as a URI query string.</returns>
 286        ///// <remarks>The query string returned includes the leading question mark.</remarks>
 287        //public string GetSharedAccessSignature(SharedAccessAccountPolicy policy)
 288        //{
 289        //    if (!this.Credentials.IsSharedKey)
 290        //    {
 291        //        var errorMessage = String.Format(CultureInfo.CurrentCulture, SR.CannotCreateSASWithoutAccountKey);
 292        //        throw new InvalidOperationException(errorMessage);
 293        //    }
 294
 295        //    StorageAccountKey accountKey = this.Credentials.Key;
 296
 297        //    string signature = SharedAccessSignatureHelper.GetHash(policy, this.Credentials.AccountName, Constants.Hea
 298        //    UriQueryBuilder builder = SharedAccessSignatureHelper.GetSignature(policy, signature, accountKey.KeyName, 
 299        //    return builder.ToString();
 300        //}
 301
 302        /// <summary>
 303        /// Returns a <see cref="StorageConnectionString"/> with development storage credentials using the specified pro
 304        /// </summary>
 305        /// <param name="proxyUri">The proxy endpoint to use.</param>
 306        /// <returns>The new <see cref="StorageConnectionString"/>.</returns>
 307        private static StorageConnectionString GetDevelopmentStorageAccount(Uri proxyUri)
 308        {
 0309            UriBuilder builder = proxyUri != null ?
 0310                new UriBuilder(proxyUri.Scheme, proxyUri.Host) :
 0311                new UriBuilder("http", "127.0.0.1");
 312
 0313            builder.Path = Constants.ConnectionStrings.DevStoreAccountName;
 314
 0315            builder.Port = Constants.ConnectionStrings.BlobEndpointPortNumber;
 0316            Uri blobEndpoint = builder.Uri;
 317
 0318            builder.Port = Constants.ConnectionStrings.TableEndpointPortNumber;
 0319            Uri tableEndpoint = builder.Uri;
 320
 0321            builder.Port = Constants.ConnectionStrings.QueueEndpointPortNumber;
 0322            Uri queueEndpoint = builder.Uri;
 323
 0324            builder.Path = Constants.ConnectionStrings.DevStoreAccountName + Constants.ConnectionStrings.SecondaryLocati
 325
 0326            builder.Port = Constants.ConnectionStrings.BlobEndpointPortNumber;
 0327            Uri blobSecondaryEndpoint = builder.Uri;
 328
 0329            builder.Port = Constants.ConnectionStrings.QueueEndpointPortNumber;
 0330            Uri queueSecondaryEndpoint = builder.Uri;
 331
 0332            builder.Port = Constants.ConnectionStrings.TableEndpointPortNumber;
 0333            Uri tableSecondaryEndpoint = builder.Uri;
 334
 0335            var credentials = new StorageSharedKeyCredential(Constants.ConnectionStrings.DevStoreAccountName, Constants.
 336#pragma warning disable IDE0017 // Simplify object initialization
 0337            var account = new StorageConnectionString(
 0338                credentials,
 0339                blobStorageUri: (blobEndpoint, blobSecondaryEndpoint),
 0340                queueStorageUri: (queueEndpoint, queueSecondaryEndpoint),
 0341                tableStorageUri: (tableEndpoint, tableSecondaryEndpoint),
 0342                fileStorageUri: (default, default) /* fileStorageUri */);
 343#pragma warning restore IDE0017 // Simplify object initialization
 344
 345#pragma warning disable IDE0028 // Simplify collection initialization
 0346            account.Settings = new Dictionary<string, string>();
 347#pragma warning restore IDE0028 // Simplify collection initialization
 0348            account.Settings.Add(Constants.ConnectionStrings.UseDevelopmentSetting, "true");
 0349            if (proxyUri != null)
 350            {
 0351                account.Settings.Add(Constants.ConnectionStrings.DevelopmentProxyUriSetting, proxyUri.ToString());
 352            }
 353
 0354            account.IsDevStoreAccount = true;
 355
 0356            return account;
 357        }
 358
 359        /// <summary>
 360        /// Internal implementation of Parse/TryParse.
 361        /// </summary>
 362        /// <param name="connectionString">The string to parse.</param>
 363        /// <param name="accountInformation">The <see cref="StorageConnectionString"/> to return.</param>
 364        /// <param name="error">A callback for reporting errors.</param>
 365        /// <returns>If true, the parse was successful. Otherwise, false.</returns>
 366        internal static bool ParseCore(string connectionString, out StorageConnectionString accountInformation, Action<s
 367        {
 0368            IDictionary<string, string> settings = ParseStringIntoSettings(connectionString, error);
 369
 370            // malformed settings string
 371
 0372            if (settings == null)
 373            {
 0374                accountInformation = null;
 375
 0376                return false;
 377            }
 378
 379            // helper method
 380
 381            string settingOrDefault(string key)
 382            {
 0383                settings.TryGetValue(key, out var result);
 384
 0385                return result;
 386            }
 387
 388            // devstore case
 389
 0390            if (MatchesSpecification(
 0391                settings,
 0392                AllRequired(s_useDevelopmentStorageSetting),
 0393                Optional(s_developmentStorageProxyUriSetting)))
 394            {
 395
 0396                accountInformation =
 0397                    settings.TryGetValue(Constants.ConnectionStrings.DevelopmentProxyUriSetting, out var proxyUri)
 0398                    ? GetDevelopmentStorageAccount(new Uri(proxyUri))
 0399                    : DevelopmentStorageAccount;
 400
 0401                accountInformation.Settings = s_validCredentials(settings);
 402
 0403                return true;
 404            }
 405
 406            // non-devstore case
 407
 0408            ConnectionStringFilter endpointsOptional =
 0409                Optional(
 0410                    s_blobEndpointSetting, s_blobSecondaryEndpointSetting,
 0411                    s_queueEndpointSetting, s_queueSecondaryEndpointSetting,
 0412                    s_fileEndpointSetting, s_fileSecondaryEndpointSetting,
 0413                    s_tableEndpointSetting, s_tableSecondaryEndpointSetting // not supported but we don't want default c
 0414                    );
 415
 0416            ConnectionStringFilter primaryEndpointRequired =
 0417                AtLeastOne(
 0418                    s_blobEndpointSetting,
 0419                    s_queueEndpointSetting,
 0420                    s_fileEndpointSetting,
 0421                    s_tableEndpointSetting
 0422                    );
 423
 0424            ConnectionStringFilter secondaryEndpointsOptional =
 0425                Optional(
 0426                    s_blobSecondaryEndpointSetting,
 0427                    s_queueSecondaryEndpointSetting,
 0428                    s_fileSecondaryEndpointSetting,
 0429                    s_tableSecondaryEndpointSetting
 0430                    );
 431
 0432            ConnectionStringFilter automaticEndpointsMatchSpec =
 0433                MatchesExactly(MatchesAll(
 0434                    MatchesOne(
 0435                        MatchesAll(AllRequired(s_accountKeySetting), Optional(s_accountKeyNameSetting)), // Key + Name, 
 0436                        AllRequired(s_sharedAccessSignatureSetting) // SAS + Name, Endpoints optional
 0437                    ),
 0438                    AllRequired(s_accountNameSetting), // Name required to automatically create URIs
 0439                    endpointsOptional,
 0440                    Optional(s_defaultEndpointsProtocolSetting, s_endpointSuffixSetting)
 0441                    ));
 442
 0443            ConnectionStringFilter explicitEndpointsMatchSpec =
 0444                MatchesExactly(MatchesAll( // Any Credentials, Endpoints must be explicitly declared
 0445                    s_validCredentials,
 0446                    primaryEndpointRequired,
 0447                    secondaryEndpointsOptional
 0448                    ));
 449
 0450            var matchesAutomaticEndpointsSpec = MatchesSpecification(settings, automaticEndpointsMatchSpec);
 0451            var matchesExplicitEndpointsSpec = MatchesSpecification(settings, explicitEndpointsMatchSpec);
 452
 0453            if (matchesAutomaticEndpointsSpec || matchesExplicitEndpointsSpec)
 454            {
 0455                if (matchesAutomaticEndpointsSpec && !settings.ContainsKey(Constants.ConnectionStrings.DefaultEndpointsP
 456                {
 0457                    settings.Add(Constants.ConnectionStrings.DefaultEndpointsProtocolSetting, "https");
 458                }
 459
 0460                var blobEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobEndpointSetting);
 0461                var queueEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueEndpointSetting);
 0462                var tableEndpoint = settingOrDefault(Constants.ConnectionStrings.TableEndpointSetting);
 0463                var fileEndpoint = settingOrDefault(Constants.ConnectionStrings.FileEndpointSetting);
 0464                var blobSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobSecondaryEndpointSetting);
 0465                var queueSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueSecondaryEndpointSetting)
 0466                var tableSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.TableSecondaryEndpointSetting)
 0467                var fileSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.FileSecondaryEndpointSetting);
 0468                var sasToken = settingOrDefault(Constants.ConnectionStrings.SharedAccessSignatureSetting);
 469
 470                // if secondary is specified, primary must also be specified
 471
 472                static bool s_isValidEndpointPair(string primary, string secondary) =>
 0473                        !string.IsNullOrWhiteSpace(primary)
 0474                        || /* primary is null, and... */ string.IsNullOrWhiteSpace(secondary);
 475
 476                (Uri, Uri) createStorageUri(string primary, string secondary, string sasToken, Func<IDictionary<string, 
 477                {
 0478                    return
 0479                        !string.IsNullOrWhiteSpace(secondary) && !string.IsNullOrWhiteSpace(primary)
 0480                            ? (CreateUri(primary, sasToken), CreateUri(secondary, sasToken))
 0481                        : !string.IsNullOrWhiteSpace(primary)
 0482                            ? (CreateUri(primary, sasToken), default)
 0483                        : matchesAutomaticEndpointsSpec && factory != null
 0484                            ? factory(settings)
 0485                        : (default, default);
 486
 487                    static Uri CreateUri(string endpoint, string sasToken)
 488                    {
 0489                        var builder = new UriBuilder(endpoint);
 0490                        if (!string.IsNullOrEmpty(builder.Query))
 491                        {
 0492                            builder.Query += "&" + sasToken;
 493                        }
 494                        else
 495                        {
 0496                            builder.Query = sasToken;
 497                        }
 0498                        return builder.Uri;
 499                    }
 500                }
 501
 0502                if (
 0503                    s_isValidEndpointPair(blobEndpoint, blobSecondaryEndpoint)
 0504                    && s_isValidEndpointPair(queueEndpoint, queueSecondaryEndpoint)
 0505                    && s_isValidEndpointPair(tableEndpoint, tableSecondaryEndpoint)
 0506                    && s_isValidEndpointPair(fileEndpoint, fileSecondaryEndpoint)
 0507                    )
 508                {
 0509                    accountInformation =
 0510                        new StorageConnectionString(
 0511                            GetCredentials(settings),
 0512                            blobStorageUri: createStorageUri(blobEndpoint, blobSecondaryEndpoint, sasToken, ConstructBlo
 0513                            queueStorageUri: createStorageUri(queueEndpoint, queueSecondaryEndpoint, sasToken, Construct
 0514                            tableStorageUri: createStorageUri(tableEndpoint, tableSecondaryEndpoint, sasToken, Construct
 0515                            fileStorageUri: createStorageUri(fileEndpoint, fileSecondaryEndpoint, sasToken, ConstructFil
 0516                            )
 0517                        {
 0518                            EndpointSuffix = settingOrDefault(Constants.ConnectionStrings.EndpointSuffixSetting),
 0519                            Settings = s_validCredentials(settings)
 0520                        };
 521
 0522                    accountInformation._accountName = settingOrDefault(Constants.ConnectionStrings.AccountNameSetting);
 523
 0524                    return true;
 525                }
 526            }
 527
 528            // not valid
 0529            accountInformation = null;
 530
 0531            error("No valid combination of account information found.");
 532
 0533            return false;
 534        }
 535
 536        /// <summary>
 537        /// Tokenizes input and stores name value pairs.
 538        /// </summary>
 539        /// <param name="connectionString">The string to parse.</param>
 540        /// <param name="error">Error reporting delegate.</param>
 541        /// <returns>Tokenized collection.</returns>
 542        private static IDictionary<string, string> ParseStringIntoSettings(string connectionString, Action<string> error
 543        {
 0544            IDictionary<string, string> settings = new Dictionary<string, string>();
 0545            var splitted = connectionString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
 546
 0547            foreach (var nameValue in splitted)
 548            {
 0549                var splittedNameValue = nameValue.Split(new char[] { '=' }, 2);
 550
 0551                if (splittedNameValue.Length != 2)
 552                {
 0553                    error("Settings must be of the form \"name=value\".");
 0554                    return null;
 555                }
 556
 0557                if (settings.ContainsKey(splittedNameValue[0]))
 558                {
 0559                    error(string.Format(CultureInfo.InvariantCulture, "Duplicate setting '{0}' found.", splittedNameValu
 0560                    return null;
 561                }
 562
 0563                settings.Add(splittedNameValue[0], splittedNameValue[1]);
 564            }
 565
 0566            return settings;
 567        }
 568
 569        /// <summary>
 570        /// Encapsulates a validation rule for an enumeration based account setting.
 571        /// </summary>
 572        /// <param name="name">The name of the setting.</param>
 573        /// <param name="validValues">A list of valid values for the setting.</param>
 574        /// <returns>An <see cref="AccountSetting"/> representing the enumeration constraint.</returns>
 575        private static AccountSetting Setting(string name, params string[] validValues) =>
 0576            new AccountSetting(
 0577                name,
 0578                (settingValue) => validValues.Length == 0 ? true : validValues.Contains(settingValue)
 0579                );
 580
 581        /// <summary>
 582        /// Encapsulates a validation rule using a func.
 583        /// </summary>
 584        /// <param name="name">The name of the setting.</param>
 585        /// <param name="isValid">A func that determines if the value is valid.</param>
 586        /// <returns>An <see cref="AccountSetting"/> representing the constraint.</returns>
 0587        private static AccountSetting Setting(string name, Func<string, bool> isValid) => new AccountSetting(name, isVal
 588
 589        /// <summary>
 590        /// Determines whether the specified setting value is a valid base64 string.
 591        /// </summary>
 592        /// <param name="settingValue">The setting value.</param>
 593        /// <returns><c>true</c> if the specified setting value is a valid base64 string; otherwise, <c>false</c>.</retu
 594        private static bool IsValidBase64String(string settingValue)
 595        {
 596            try
 597            {
 0598                Convert.FromBase64String(settingValue);
 599
 0600                return true;
 601            }
 0602            catch (FormatException)
 603            {
 0604                return false;
 605            }
 0606        }
 607
 608        /// <summary>
 609        /// Validation function that validates Uris.
 610        /// </summary>
 611        /// <param name="settingValue">Value to validate.</param>
 612        /// <returns><c>true</c> if the specified setting value is a valid Uri; otherwise, <c>false</c>.</returns>
 0613        private static bool IsValidUri(string settingValue) => Uri.IsWellFormedUriString(settingValue, UriKind.Absolute)
 614
 615        /// <summary>
 616        /// Validation function that validates a domain name.
 617        /// </summary>
 618        /// <param name="settingValue">Value to validate.</param>
 619        /// <returns><c>true</c> if the specified setting value is a valid domain; otherwise, <c>false</c>.</returns>
 0620        private static bool IsValidDomain(string settingValue) => Uri.CheckHostName(settingValue).Equals(UriHostNameType
 621
 622        /// <summary>
 623        /// Settings filter that requires all specified settings be present and valid.
 624        /// </summary>
 625        /// <param name="requiredSettings">A list of settings that must be present.</param>
 626        /// <returns>The remaining settings or <c>null</c> if the filter's requirement is not satisfied.</returns>
 627        private static ConnectionStringFilter AllRequired(params AccountSetting[] requiredSettings) =>
 0628            (settings) =>
 0629            {
 0630                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 0631
 0632                foreach (AccountSetting requirement in requiredSettings)
 0633                {
 0634                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 0635                    {
 0636                        result.Remove(requirement.Key);
 0637                    }
 0638                    else
 0639                    {
 0640                        return null;
 0641                    }
 0642                }
 0643
 0644                return result;
 0645            };
 646
 647        /// <summary>
 648        /// Settings filter that removes optional values.
 649        /// </summary>
 650        /// <param name="optionalSettings">A list of settings that are optional.</param>
 651        /// <returns>The remaining settings or <c>null</c> if the filter's requirement is not satisfied.</returns>
 652        private static ConnectionStringFilter Optional(params AccountSetting[] optionalSettings) =>
 0653            (settings) =>
 0654            {
 0655                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 0656
 0657                foreach (AccountSetting requirement in optionalSettings)
 0658                {
 0659                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 0660                    {
 0661                        result.Remove(requirement.Key);
 0662                    }
 0663                }
 0664
 0665                return result;
 0666            };
 667
 668        /// <summary>
 669        /// Settings filter that ensures that at least one setting is present.
 670        /// </summary>
 671        /// <param name="atLeastOneSettings">A list of settings of which one must be present.</param>
 672        /// <returns>The remaining settings or <c>null</c> if the filter's requirement is not satisfied.</returns>
 673        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = 
 674        private static ConnectionStringFilter AtLeastOne(params AccountSetting[] atLeastOneSettings) =>
 0675            (settings) =>
 0676            {
 0677                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 0678                var foundOne = false;
 0679
 0680                foreach (AccountSetting requirement in atLeastOneSettings)
 0681                {
 0682                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 0683                    {
 0684                        result.Remove(requirement.Key);
 0685                        foundOne = true;
 0686                    }
 0687                }
 0688
 0689                return foundOne ? result : null;
 0690            };
 691
 692        /// <summary>
 693        /// Settings filter that ensures that none of the specified settings are present.
 694        /// </summary>
 695        /// <param name="atLeastOneSettings">A list of settings of which one must not be present.</param>
 696        /// <returns>The remaining settings or <c>null</c> if the filter's requirement is not satisfied.</returns>
 697        [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = 
 698        private static ConnectionStringFilter None(params AccountSetting[] atLeastOneSettings) =>
 0699            (settings) =>
 0700            {
 0701                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 0702                var foundOne = false;
 0703
 0704                foreach (AccountSetting requirement in atLeastOneSettings)
 0705                {
 0706                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 0707                    {
 0708                        foundOne = true;
 0709                    }
 0710                }
 0711
 0712                return foundOne ? null : result;
 0713            };
 714
 715        /// <summary>
 716        /// Settings filter that ensures that all of the specified filters match.
 717        /// </summary>
 718        /// <param name="filters">A list of filters of which all must match.</param>
 719        /// <returns>The remaining settings or <c>null</c> if the filter's requirement is not satisfied.</returns>
 720        private static ConnectionStringFilter MatchesAll(params ConnectionStringFilter[] filters) =>
 0721            (settings) =>
 0722            {
 0723                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 0724
 0725                foreach (ConnectionStringFilter filter in filters)
 0726                {
 0727                    if (result == null)
 0728                    {
 0729                        break;
 0730                    }
 0731
 0732                    result = filter(result);
 0733                }
 0734
 0735                return result;
 0736            };
 737
 738        /// <summary>
 739        /// Settings filter that ensures that exactly one filter matches.
 740        /// </summary>
 741        /// <param name="filters">A list of filters of which exactly one must match.</param>
 742        /// <returns>The remaining settings or <c>null</c> if the filter's requirement is not satisfied.</returns>
 743        private static ConnectionStringFilter MatchesOne(params ConnectionStringFilter[] filters) =>
 0744            (settings) =>
 0745            {
 0746                IDictionary<string, string>[] results =
 0747                    filters
 0748                    .Select(filter => filter(new Dictionary<string, string>(settings)))
 0749                    .Where(result => result != null)
 0750                    .Take(2)
 0751                    .ToArray();
 0752
 0753                return results.Length != 1 ? null : results.First();
 0754            };
 755
 756        /// <summary>
 757        /// Settings filter that ensures that the specified filter is an exact match.
 758        /// </summary>
 759        /// <param name="filter">A list of filters of which ensures that the specified filter is an exact match.</param>
 760        /// <returns>The remaining settings or <c>null</c> if the filter's requirement is not satisfied.</returns>
 761        private static ConnectionStringFilter MatchesExactly(ConnectionStringFilter filter) =>
 0762            (settings) =>
 0763            {
 0764                IDictionary<string, string> results = filter(settings);
 0765
 0766                return results == null || results.Any() ? null : results;
 0767            };
 768
 769        /// <summary>
 770        /// Settings filter that ensures that a valid combination of credentials is present.
 771        /// </summary>
 772        /// <returns>The remaining settings or <c>null</c> if the filter's requirement is not satisfied.</returns>
 0773        private static readonly ConnectionStringFilter s_validCredentials =
 0774            MatchesOne(
 0775                MatchesAll(AllRequired(s_accountNameSetting, s_accountKeySetting), Optional(s_accountKeyNameSetting), No
 0776                MatchesAll(AllRequired(s_sharedAccessSignatureSetting), Optional(s_accountNameSetting), None(s_accountKe
 0777                None(s_accountNameSetting, s_accountKeySetting, s_accountKeyNameSetting, s_sharedAccessSignatureSetting)
 0778            );
 779
 780        /// <summary>
 781        /// Tests to see if a given list of settings matches a set of filters exactly.
 782        /// </summary>
 783        /// <param name="settings">The settings to check.</param>
 784        /// <param name="constraints">A list of filters to check.</param>
 785        /// <returns>
 786        /// If any filter returns null, false.
 787        /// If there are any settings left over after all filters are processed, false.
 788        /// Otherwise true.
 789        /// </returns>
 790        private static bool MatchesSpecification(
 791            IDictionary<string, string> settings,
 792            params ConnectionStringFilter[] constraints)
 793        {
 0794            foreach (ConnectionStringFilter constraint in constraints)
 795            {
 0796                IDictionary<string, string> remainingSettings = constraint(settings);
 797
 0798                if (remainingSettings == null)
 799                {
 0800                    return false;
 801                }
 802                else
 803                {
 0804                    settings = remainingSettings;
 805                }
 806            }
 807
 0808            return !settings.Any();
 809        }
 810
 811        /// <summary>
 812        /// Gets a StorageCredentials object corresponding to whatever credentials are supplied in the given settings.
 813        /// </summary>
 814        /// <param name="settings">The settings to check.</param>
 815        /// <returns>The StorageCredentials object specified in the settings.</returns>
 816        private static object GetCredentials(IDictionary<string, string> settings)
 817        {
 818
 0819            settings.TryGetValue(Constants.ConnectionStrings.AccountNameSetting, out var accountName);
 0820            settings.TryGetValue(Constants.ConnectionStrings.AccountKeySetting, out var accountKey);
 0821            settings.TryGetValue(Constants.ConnectionStrings.SharedAccessSignatureSetting, out var sharedAccessSignature
 822
 823            // The accountKeyName isn't used
 824            //settings.TryGetValue(Constants.ConnectionStrings.AccountKeyNameSetting, out var accountKeyName);
 825
 0826            return
 0827                accountName != null && accountKey != null && sharedAccessSignature == null
 0828                ? new StorageSharedKeyCredential(accountName, accountKey/*, accountKeyName */)
 0829                : (object)(accountKey == null /* && accountKeyName == null */ && sharedAccessSignature != null
 0830                    ? new SharedAccessSignatureCredentials(sharedAccessSignature)
 0831                    : null);
 832        }
 833
 834        /// <summary>
 835        /// Gets the default blob endpoint using specified settings.
 836        /// </summary>
 837        /// <param name="settings">The settings to use.</param>
 838        /// <returns>The default blob endpoint.</returns>
 0839        private static (Uri, Uri) ConstructBlobEndpoint(IDictionary<string, string> settings) => ConstructBlobEndpoint(
 0840                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 0841                settings[Constants.ConnectionStrings.AccountNameSetting],
 0842                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 0843                settings.ContainsKey(Constants.ConnectionStrings.SharedAccessSignatureSetting) ? settings[Constants.Conn
 844
 845        /// <summary>
 846        /// Gets the default blob endpoint using the specified protocol and account name.
 847        /// </summary>
 848        /// <param name="scheme">The protocol to use.</param>
 849        /// <param name="accountName">The name of the storage account.</param>
 850        /// <param name="endpointSuffix">The Endpoint DNS suffix; use <c>null</c> for default.</param>
 851        /// <param name="sasToken">The sas token; use <c>null</c> for default.</param>
 852        /// <returns>The default blob endpoint.</returns>
 853        internal static (Uri, Uri) ConstructBlobEndpoint(string scheme, string accountName, string endpointSuffix, strin
 854        {
 0855            if (string.IsNullOrEmpty(scheme))
 856            {
 0857                throw Errors.ArgumentNull(nameof(scheme));
 858            }
 859
 0860            if (string.IsNullOrEmpty(accountName))
 861            {
 0862                throw Errors.ArgumentNull(nameof(accountName));
 863            }
 864
 0865            if (string.IsNullOrEmpty(endpointSuffix))
 866            {
 0867                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 868            }
 869
 0870            return ConstructUris(scheme, accountName, Constants.ConnectionStrings.DefaultBlobHostnamePrefix, endpointSuf
 871        }
 872
 873        /// <summary>
 874        /// Gets the default file endpoint using specified settings.
 875        /// </summary>
 876        /// <param name="settings">The settings to use.</param>
 877        /// <returns>The default file endpoint.</returns>
 0878        private static (Uri, Uri) ConstructFileEndpoint(IDictionary<string, string> settings) => ConstructFileEndpoint(
 0879                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 0880                settings[Constants.ConnectionStrings.AccountNameSetting],
 0881                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 0882                settings.ContainsKey(Constants.ConnectionStrings.SharedAccessSignatureSetting) ? settings[Constants.Conn
 883
 884        /// <summary>
 885        /// Gets the default file endpoint using the specified protocol and account name.
 886        /// </summary>
 887        /// <param name="scheme">The protocol to use.</param>
 888        /// <param name="accountName">The name of the storage account.</param>
 889        /// <param name="endpointSuffix">The Endpoint DNS suffix; use <c>null</c> for default.</param>
 890        /// <param name="sasToken">The sas token; use <c>null</c> for default.</param>
 891        /// <returns>The default file endpoint.</returns>
 892        internal static (Uri, Uri) ConstructFileEndpoint(string scheme, string accountName, string endpointSuffix, strin
 893        {
 0894            if (string.IsNullOrEmpty(scheme))
 895            {
 0896                throw Errors.ArgumentNull(nameof(scheme));
 897            }
 898
 0899            if (string.IsNullOrEmpty(accountName))
 900            {
 0901                throw Errors.ArgumentNull(nameof(accountName));
 902            }
 903
 0904            if (string.IsNullOrEmpty(endpointSuffix))
 905            {
 0906                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 907            }
 908
 0909            return ConstructUris(scheme, accountName, Constants.ConnectionStrings.DefaultFileHostnamePrefix, endpointSuf
 910        }
 911
 912        /// <summary>
 913        /// Gets the default queue endpoint using the specified settings.
 914        /// </summary>
 915        /// <param name="settings">The settings.</param>
 916        /// <returns>The default queue endpoint.</returns>
 0917        private static (Uri, Uri) ConstructQueueEndpoint(IDictionary<string, string> settings) => ConstructQueueEndpoint
 0918                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 0919                settings[Constants.ConnectionStrings.AccountNameSetting],
 0920                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 0921                settings.ContainsKey(Constants.ConnectionStrings.SharedAccessSignatureSetting) ? settings[Constants.Conn
 922
 923        /// <summary>
 924        /// Gets the default queue endpoint using the specified protocol and account name.
 925        /// </summary>
 926        /// <param name="scheme">The protocol to use.</param>
 927        /// <param name="accountName">The name of the storage account.</param>
 928        /// <param name="endpointSuffix">The Endpoint DNS suffix; use <c>null</c> for default.</param>
 929        /// <param name="sasToken">The sas token; use <c>null</c> for default.</param>
 930        /// <returns>The default queue endpoint.</returns>
 931        internal static (Uri, Uri) ConstructQueueEndpoint(string scheme, string accountName, string endpointSuffix, stri
 932        {
 0933            if (string.IsNullOrEmpty(scheme))
 934            {
 0935                throw Errors.ArgumentNull(nameof(scheme));
 936            }
 937
 0938            if (string.IsNullOrEmpty(accountName))
 939            {
 0940                throw Errors.ArgumentNull(nameof(accountName));
 941            }
 942
 0943            if (string.IsNullOrEmpty(endpointSuffix))
 944            {
 0945                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 946            }
 947
 0948            return ConstructUris(scheme, accountName, Constants.ConnectionStrings.DefaultQueueHostnamePrefix, endpointSu
 949        }
 950
 951        /// <summary>
 952        /// Gets the default table endpoint using the specified settings.
 953        /// </summary>
 954        /// <param name="settings">The settings.</param>
 955        /// <returns>The default table endpoint.</returns>
 0956        private static (Uri, Uri) ConstructTableEndpoint(IDictionary<string, string> settings) => ConstructTableEndpoint
 0957                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 0958                settings[Constants.ConnectionStrings.AccountNameSetting],
 0959                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 0960                settings.ContainsKey(Constants.ConnectionStrings.SharedAccessSignatureSetting) ? settings[Constants.Conn
 961
 962        /// <summary>
 963        /// Gets the default queue endpoint using the specified protocol and account name.
 964        /// </summary>
 965        /// <param name="scheme">The protocol to use.</param>
 966        /// <param name="accountName">The name of the storage account.</param>
 967        /// <param name="endpointSuffix">The Endpoint DNS suffix; use <c>null</c> for default.</param>
 968        /// <param name="sasToken">The sas token; use <c>null</c> for default.</param>
 969        /// <returns>The default table endpoint.</returns>
 970        internal static (Uri, Uri) ConstructTableEndpoint(string scheme, string accountName, string endpointSuffix, stri
 971        {
 0972            if (string.IsNullOrEmpty(scheme))
 973            {
 0974                throw Errors.ArgumentNull(nameof(scheme));
 975            }
 976
 0977            if (string.IsNullOrEmpty(accountName))
 978            {
 0979                throw Errors.ArgumentNull(nameof(accountName));
 980            }
 981
 0982            if (string.IsNullOrEmpty(endpointSuffix))
 983            {
 0984                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 985            }
 986
 0987            return ConstructUris(scheme, accountName, Constants.ConnectionStrings.DefaultTableHostnamePrefix, endpointSu
 988        }
 989
 990        /// <summary>
 991        /// Construct the Primary/Secondary Uri tuple.
 992        /// </summary>
 993        /// <param name="scheme">The protocol to use.</param>
 994        /// <param name="accountName">The name of the storage account.</param>
 995        /// <param name="hostNamePrefix">Prefix that appears before the host name, e.g. "blob".</param>
 996        /// <param name="endpointSuffix">The Endpoint DNS suffix; use <c>null</c> for default.</param>
 997        /// <param name="sasToken">The sas token; use <c>null</c> for default.</param>
 998        /// <returns></returns>
 999        private static (Uri, Uri) ConstructUris(
 1000            string scheme,
 1001            string accountName,
 1002            string hostNamePrefix,
 1003            string endpointSuffix,
 1004            string sasToken)
 1005        {
 01006            var primaryUriBuilder = new UriBuilder
 01007            {
 01008                Scheme = scheme,
 01009                Host = string.Format(
 01010                        CultureInfo.InvariantCulture,
 01011                        "{0}.{1}.{2}",
 01012                        accountName,
 01013                        hostNamePrefix,
 01014                        endpointSuffix),
 01015                Query = sasToken
 01016            };
 1017
 01018            var secondaryUriBuilder = new UriBuilder
 01019            {
 01020                Scheme = scheme,
 01021                Host = string.Format(
 01022                        CultureInfo.InvariantCulture,
 01023                        "{0}{1}.{2}.{3}",
 01024                        accountName,
 01025                        Constants.ConnectionStrings.SecondaryLocationAccountSuffix,
 01026                        hostNamePrefix,
 01027                        endpointSuffix),
 01028                Query = sasToken
 01029            };
 1030
 01031            return (primaryUriBuilder.Uri, secondaryUriBuilder.Uri);
 1032        }
 1033    }
 1034}