< Summary

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

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
get_UseV1MD5()-0%100%
.cctor()-94.04%91.3%
.ctor(...)-100%100%
get_DevelopmentStorageAccount()-100%100%
get_IsDevStoreAccount()-100%100%
get_EndpointSuffix()-0%100%
get_Settings()-100%100%
get_DefaultEndpoints()-100%100%
get_BlobEndpoint()-100%100%
get_QueueEndpoint()-100%100%
get_TableEndpoint()-0%100%
get_FileEndpoint()-100%100%
get_BlobStorageUri()-100%100%
get_QueueStorageUri()-100%100%
get_TableStorageUri()-0%100%
get_FileStorageUri()-100%100%
get_Credentials()-100%100%
Parse(...)-40%50%
TryParse(...)-62.5%100%
GetDevelopmentStorageAccount(...)-100%100%
ParseCore(...)-97.14%88.24%
ParseStringIntoSettings(...)-66.67%66.67%
Setting(...)-100%100%
Setting(...)-100%100%
IsValidBase64String(...)-60%100%
IsValidUri(...)-100%100%
IsValidDomain(...)-0%100%
AllRequired(...)-100%100%
Optional(...)-100%100%
AtLeastOne(...)-100%100%
None(...)-100%100%
MatchesAll(...)-100%100%
MatchesOne(...)-100%100%
MatchesExactly(...)-100%100%
MatchesSpecification(...)-100%100%
GetCredentials(...)-100%90%
ConstructBlobEndpoint(...)-100%50%
ConstructBlobEndpoint(...)-71.43%66.67%
ConstructFileEndpoint(...)-100%50%
ConstructFileEndpoint(...)-71.43%66.67%
ConstructQueueEndpoint(...)-100%50%
ConstructQueueEndpoint(...)-71.43%66.67%
ConstructTableEndpoint(...)-100%50%
ConstructTableEndpoint(...)-71.43%66.67%
ConstructUris(...)-100%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>
 227        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>
 232        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>
 237        private static readonly AccountSetting s_defaultEndpointsProtocolSetting = Setting(Constants.ConnectionStrings.D
 38
 39        /// <summary>
 40        /// Validator for the AccountName setting. No restrictions.
 41        /// </summary>
 242        private static readonly AccountSetting s_accountNameSetting = Setting(Constants.ConnectionStrings.AccountNameSet
 43
 44        /// <summary>
 45        /// Validator for the AccountKey setting. No restrictions.
 46        /// </summary>
 247        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>
 252        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>
 257        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>
 262        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>
 267        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>
 272        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>
 277        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>
 282        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>
 287        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>
 292        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>
 297        private static readonly AccountSetting s_endpointSuffixSetting = Setting(Constants.ConnectionStrings.EndpointSuf
 98
 99        /// <summary>
 100        /// Validator for the SharedAccessSignature setting. No restrictions.
 101        /// </summary>
 2102        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.</
 18118        public StorageConnectionString(
 18119            object storageCredentials,
 18120            (Uri, Uri) blobStorageUri = default,
 18121            (Uri, Uri) queueStorageUri = default,
 18122            (Uri, Uri) tableStorageUri = default,
 18123            (Uri, Uri) fileStorageUri = default)
 124        {
 18125            Credentials = storageCredentials;
 18126            BlobStorageUri = blobStorageUri;
 18127            QueueStorageUri = queueStorageUri;
 18128            TableStorageUri = tableStorageUri;
 18129            FileStorageUri = fileStorageUri;
 18130            DefaultEndpoints = false;
 18131        }
 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            {
 10142                if (s_devStoreAccount == null)
 143                {
 2144                    s_devStoreAccount = GetDevelopmentStorageAccount(null);
 145                }
 146
 10147                return s_devStoreAccount;
 148            }
 149        }
 150
 151        /// <summary>
 152        /// Indicates whether this account is a development storage account.
 153        /// </summary>
 22154        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>
 96164        internal IDictionary<string, string> Settings { get; set; }
 165
 166        /// <summary>
 167        /// True if the user used a constructor that auto-generates endpoints.
 168        /// </summary>
 20169        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>
 18175        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>
 14181        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>
 10193        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>
 56199        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>
 50205        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>
 46217        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>
 66223        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
 14242            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            {
 14249                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        {
 16264            if (string.IsNullOrEmpty(connectionString))
 265            {
 4266                account = null;
 4267                return false;
 268            }
 269
 270            try
 271            {
 18272                return ParseCore(connectionString, out account, err => { });
 273            }
 0274            catch (Exception)
 275            {
 0276                account = null;
 0277                return false;
 278            }
 12279        }
 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        {
 6309            UriBuilder builder = proxyUri != null ?
 6310                new UriBuilder(proxyUri.Scheme, proxyUri.Host) :
 6311                new UriBuilder("http", "127.0.0.1");
 312
 6313            builder.Path = Constants.ConnectionStrings.DevStoreAccountName;
 314
 6315            builder.Port = Constants.ConnectionStrings.BlobEndpointPortNumber;
 6316            Uri blobEndpoint = builder.Uri;
 317
 6318            builder.Port = Constants.ConnectionStrings.TableEndpointPortNumber;
 6319            Uri tableEndpoint = builder.Uri;
 320
 6321            builder.Port = Constants.ConnectionStrings.QueueEndpointPortNumber;
 6322            Uri queueEndpoint = builder.Uri;
 323
 6324            builder.Path = Constants.ConnectionStrings.DevStoreAccountName + Constants.ConnectionStrings.SecondaryLocati
 325
 6326            builder.Port = Constants.ConnectionStrings.BlobEndpointPortNumber;
 6327            Uri blobSecondaryEndpoint = builder.Uri;
 328
 6329            builder.Port = Constants.ConnectionStrings.QueueEndpointPortNumber;
 6330            Uri queueSecondaryEndpoint = builder.Uri;
 331
 6332            builder.Port = Constants.ConnectionStrings.TableEndpointPortNumber;
 6333            Uri tableSecondaryEndpoint = builder.Uri;
 334
 6335            var credentials = new StorageSharedKeyCredential(Constants.ConnectionStrings.DevStoreAccountName, Constants.
 336#pragma warning disable IDE0017 // Simplify object initialization
 6337            var account = new StorageConnectionString(
 6338                credentials,
 6339                blobStorageUri: (blobEndpoint, blobSecondaryEndpoint),
 6340                queueStorageUri: (queueEndpoint, queueSecondaryEndpoint),
 6341                tableStorageUri: (tableEndpoint, tableSecondaryEndpoint),
 6342                fileStorageUri: (default, default) /* fileStorageUri */);
 343#pragma warning restore IDE0017 // Simplify object initialization
 344
 345#pragma warning disable IDE0028 // Simplify collection initialization
 6346            account.Settings = new Dictionary<string, string>();
 347#pragma warning restore IDE0028 // Simplify collection initialization
 6348            account.Settings.Add(Constants.ConnectionStrings.UseDevelopmentSetting, "true");
 6349            if (proxyUri != null)
 350            {
 4351                account.Settings.Add(Constants.ConnectionStrings.DevelopmentProxyUriSetting, proxyUri.ToString());
 352            }
 353
 6354            account.IsDevStoreAccount = true;
 355
 6356            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        {
 26368            IDictionary<string, string> settings = ParseStringIntoSettings(connectionString, error);
 369
 370            // malformed settings string
 371
 26372            if (settings == null)
 373            {
 0374                accountInformation = null;
 375
 0376                return false;
 377            }
 378
 379            // helper method
 380
 381            string settingOrDefault(string key)
 382            {
 110383                settings.TryGetValue(key, out var result);
 384
 110385                return result;
 386            }
 387
 388            // devstore case
 389
 26390            if (MatchesSpecification(
 26391                settings,
 26392                AllRequired(s_useDevelopmentStorageSetting),
 26393                Optional(s_developmentStorageProxyUriSetting)))
 394            {
 395
 10396                accountInformation =
 10397                    settings.TryGetValue(Constants.ConnectionStrings.DevelopmentProxyUriSetting, out var proxyUri)
 10398                    ? GetDevelopmentStorageAccount(new Uri(proxyUri))
 10399                    : DevelopmentStorageAccount;
 400
 10401                accountInformation.Settings = s_validCredentials(settings);
 402
 10403                return true;
 404            }
 405
 406            // non-devstore case
 407
 16408            ConnectionStringFilter endpointsOptional =
 16409                Optional(
 16410                    s_blobEndpointSetting, s_blobSecondaryEndpointSetting,
 16411                    s_queueEndpointSetting, s_queueSecondaryEndpointSetting,
 16412                    s_fileEndpointSetting, s_fileSecondaryEndpointSetting,
 16413                    s_tableEndpointSetting, s_tableSecondaryEndpointSetting // not supported but we don't want default c
 16414                    );
 415
 16416            ConnectionStringFilter primaryEndpointRequired =
 16417                AtLeastOne(
 16418                    s_blobEndpointSetting,
 16419                    s_queueEndpointSetting,
 16420                    s_fileEndpointSetting,
 16421                    s_tableEndpointSetting
 16422                    );
 423
 16424            ConnectionStringFilter secondaryEndpointsOptional =
 16425                Optional(
 16426                    s_blobSecondaryEndpointSetting,
 16427                    s_queueSecondaryEndpointSetting,
 16428                    s_fileSecondaryEndpointSetting,
 16429                    s_tableSecondaryEndpointSetting
 16430                    );
 431
 16432            ConnectionStringFilter automaticEndpointsMatchSpec =
 16433                MatchesExactly(MatchesAll(
 16434                    MatchesOne(
 16435                        MatchesAll(AllRequired(s_accountKeySetting), Optional(s_accountKeyNameSetting)), // Key + Name, 
 16436                        AllRequired(s_sharedAccessSignatureSetting) // SAS + Name, Endpoints optional
 16437                    ),
 16438                    AllRequired(s_accountNameSetting), // Name required to automatically create URIs
 16439                    endpointsOptional,
 16440                    Optional(s_defaultEndpointsProtocolSetting, s_endpointSuffixSetting)
 16441                    ));
 442
 16443            ConnectionStringFilter explicitEndpointsMatchSpec =
 16444                MatchesExactly(MatchesAll( // Any Credentials, Endpoints must be explicitly declared
 16445                    s_validCredentials,
 16446                    primaryEndpointRequired,
 16447                    secondaryEndpointsOptional
 16448                    ));
 449
 16450            var matchesAutomaticEndpointsSpec = MatchesSpecification(settings, automaticEndpointsMatchSpec);
 16451            var matchesExplicitEndpointsSpec = MatchesSpecification(settings, explicitEndpointsMatchSpec);
 452
 16453            if (matchesAutomaticEndpointsSpec || matchesExplicitEndpointsSpec)
 454            {
 10455                if (matchesAutomaticEndpointsSpec && !settings.ContainsKey(Constants.ConnectionStrings.DefaultEndpointsP
 456                {
 2457                    settings.Add(Constants.ConnectionStrings.DefaultEndpointsProtocolSetting, "https");
 458                }
 459
 10460                var blobEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobEndpointSetting);
 10461                var queueEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueEndpointSetting);
 10462                var tableEndpoint = settingOrDefault(Constants.ConnectionStrings.TableEndpointSetting);
 10463                var fileEndpoint = settingOrDefault(Constants.ConnectionStrings.FileEndpointSetting);
 10464                var blobSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobSecondaryEndpointSetting);
 10465                var queueSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueSecondaryEndpointSetting)
 10466                var tableSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.TableSecondaryEndpointSetting)
 10467                var fileSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.FileSecondaryEndpointSetting);
 10468                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) =>
 40473                        !string.IsNullOrWhiteSpace(primary)
 40474                        || /* primary is null, and... */ string.IsNullOrWhiteSpace(secondary);
 475
 476                (Uri, Uri) createStorageUri(string primary, string secondary, string sasToken, Func<IDictionary<string, 
 477                {
 40478                    return
 40479                        !string.IsNullOrWhiteSpace(secondary) && !string.IsNullOrWhiteSpace(primary)
 40480                            ? (CreateUri(primary, sasToken), CreateUri(secondary, sasToken))
 40481                        : !string.IsNullOrWhiteSpace(primary)
 40482                            ? (CreateUri(primary, sasToken), default)
 40483                        : matchesAutomaticEndpointsSpec && factory != null
 40484                            ? factory(settings)
 40485                        : (default, default);
 486
 487                    static Uri CreateUri(string endpoint, string sasToken)
 488                    {
 8489                        var builder = new UriBuilder(endpoint);
 8490                        if (!string.IsNullOrEmpty(builder.Query))
 491                        {
 0492                            builder.Query += "&" + sasToken;
 493                        }
 494                        else
 495                        {
 8496                            builder.Query = sasToken;
 497                        }
 8498                        return builder.Uri;
 499                    }
 500                }
 501
 10502                if (
 10503                    s_isValidEndpointPair(blobEndpoint, blobSecondaryEndpoint)
 10504                    && s_isValidEndpointPair(queueEndpoint, queueSecondaryEndpoint)
 10505                    && s_isValidEndpointPair(tableEndpoint, tableSecondaryEndpoint)
 10506                    && s_isValidEndpointPair(fileEndpoint, fileSecondaryEndpoint)
 10507                    )
 508                {
 10509                    accountInformation =
 10510                        new StorageConnectionString(
 10511                            GetCredentials(settings),
 10512                            blobStorageUri: createStorageUri(blobEndpoint, blobSecondaryEndpoint, sasToken, ConstructBlo
 10513                            queueStorageUri: createStorageUri(queueEndpoint, queueSecondaryEndpoint, sasToken, Construct
 10514                            tableStorageUri: createStorageUri(tableEndpoint, tableSecondaryEndpoint, sasToken, Construct
 10515                            fileStorageUri: createStorageUri(fileEndpoint, fileSecondaryEndpoint, sasToken, ConstructFil
 10516                            )
 10517                        {
 10518                            EndpointSuffix = settingOrDefault(Constants.ConnectionStrings.EndpointSuffixSetting),
 10519                            Settings = s_validCredentials(settings)
 10520                        };
 521
 10522                    accountInformation._accountName = settingOrDefault(Constants.ConnectionStrings.AccountNameSetting);
 523
 10524                    return true;
 525                }
 526            }
 527
 528            // not valid
 6529            accountInformation = null;
 530
 6531            error("No valid combination of account information found.");
 532
 6533            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        {
 26544            IDictionary<string, string> settings = new Dictionary<string, string>();
 26545            var splitted = connectionString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
 546
 148547            foreach (var nameValue in splitted)
 548            {
 48549                var splittedNameValue = nameValue.Split(new char[] { '=' }, 2);
 550
 48551                if (splittedNameValue.Length != 2)
 552                {
 0553                    error("Settings must be of the form \"name=value\".");
 0554                    return null;
 555                }
 556
 48557                if (settings.ContainsKey(splittedNameValue[0]))
 558                {
 0559                    error(string.Format(CultureInfo.InvariantCulture, "Duplicate setting '{0}' found.", splittedNameValu
 0560                    return null;
 561                }
 562
 48563                settings.Add(splittedNameValue[0], splittedNameValue[1]);
 564            }
 565
 26566            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) =>
 10576            new AccountSetting(
 10577                name,
 64578                (settingValue) => validValues.Length == 0 ? true : validValues.Contains(settingValue)
 10579                );
 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>
 22587        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            {
 30598                Convert.FromBase64String(settingValue);
 599
 30600                return true;
 601            }
 0602            catch (FormatException)
 603            {
 0604                return false;
 605            }
 30606        }
 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>
 18613        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) =>
 78628            (settings) =>
 78629            {
 214630                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 78631
 548632                foreach (AccountSetting requirement in requiredSettings)
 78633                {
 228634                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 78635                    {
 126636                        result.Remove(requirement.Key);
 78637                    }
 78638                    else
 78639                    {
 180640                        return null;
 78641                    }
 78642                }
 78643
 112644                return result;
 78645            };
 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) =>
 94653            (settings) =>
 94654            {
 144655                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 94656
 450657                foreach (AccountSetting requirement in optionalSettings)
 94658                {
 222659                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 94660                    {
 106661                        result.Remove(requirement.Key);
 94662                    }
 94663                }
 94664
 144665                return result;
 94666            };
 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) =>
 16675            (settings) =>
 16676            {
 30677                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 30678                var foundOne = false;
 16679
 156680                foreach (AccountSetting requirement in atLeastOneSettings)
 16681                {
 72682                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 16683                    {
 26684                        result.Remove(requirement.Key);
 26685                        foundOne = true;
 16686                    }
 16687                }
 16688
 30689                return foundOne ? result : null;
 16690            };
 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) =>
 6699            (settings) =>
 6700            {
 54701                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 54702                var foundOne = false;
 6703
 414704                foreach (AccountSetting requirement in atLeastOneSettings)
 6705                {
 162706                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 6707                    {
 32708                        foundOne = true;
 6709                    }
 6710                }
 6711
 54712                return foundOne ? null : result;
 6713            };
 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) =>
 52721            (settings) =>
 52722            {
 172723                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 52724
 762725                foreach (ConnectionStringFilter filter in filters)
 52726                {
 330727                    if (result == null)
 52728                    {
 52729                        break;
 52730                    }
 52731
 244732                    result = filter(result);
 52733                }
 52734
 172735                return result;
 52736            };
 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) =>
 18744            (settings) =>
 18745            {
 70746                IDictionary<string, string>[] results =
 70747                    filters
 210748                    .Select(filter => filter(new Dictionary<string, string>(settings)))
 210749                    .Where(result => result != null)
 70750                    .Take(2)
 70751                    .ToArray();
 18752
 70753                return results.Length != 1 ? null : results.First();
 18754            };
 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) =>
 32762            (settings) =>
 32763            {
 64764                IDictionary<string, string> results = filter(settings);
 32765
 64766                return results == null || results.Any() ? null : results;
 32767            };
 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>
 2773        private static readonly ConnectionStringFilter s_validCredentials =
 2774            MatchesOne(
 2775                MatchesAll(AllRequired(s_accountNameSetting, s_accountKeySetting), Optional(s_accountKeyNameSetting), No
 2776                MatchesAll(AllRequired(s_sharedAccessSignatureSetting), Optional(s_accountNameSetting), None(s_accountKe
 2777                None(s_accountNameSetting, s_accountKeySetting, s_accountKeyNameSetting, s_sharedAccessSignatureSetting)
 2778            );
 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        {
 216794            foreach (ConnectionStringFilter constraint in constraints)
 795            {
 68796                IDictionary<string, string> remainingSettings = constraint(settings);
 797
 68798                if (remainingSettings == null)
 799                {
 36800                    return false;
 801                }
 802                else
 803                {
 32804                    settings = remainingSettings;
 805                }
 806            }
 807
 22808            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
 10819            settings.TryGetValue(Constants.ConnectionStrings.AccountNameSetting, out var accountName);
 10820            settings.TryGetValue(Constants.ConnectionStrings.AccountKeySetting, out var accountKey);
 10821            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
 10826            return
 10827                accountName != null && accountKey != null && sharedAccessSignature == null
 10828                ? new StorageSharedKeyCredential(accountName, accountKey/*, accountKeyName */)
 10829                : (object)(accountKey == null /* && accountKeyName == null */ && sharedAccessSignature != null
 10830                    ? new SharedAccessSignatureCredentials(sharedAccessSignature)
 10831                    : 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>
 2839        private static (Uri, Uri) ConstructBlobEndpoint(IDictionary<string, string> settings) => ConstructBlobEndpoint(
 2840                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 2841                settings[Constants.ConnectionStrings.AccountNameSetting],
 2842                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 2843                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        {
 2855            if (string.IsNullOrEmpty(scheme))
 856            {
 0857                throw Errors.ArgumentNull(nameof(scheme));
 858            }
 859
 2860            if (string.IsNullOrEmpty(accountName))
 861            {
 0862                throw Errors.ArgumentNull(nameof(accountName));
 863            }
 864
 2865            if (string.IsNullOrEmpty(endpointSuffix))
 866            {
 2867                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 868            }
 869
 2870            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>
 6878        private static (Uri, Uri) ConstructFileEndpoint(IDictionary<string, string> settings) => ConstructFileEndpoint(
 6879                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 6880                settings[Constants.ConnectionStrings.AccountNameSetting],
 6881                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 6882                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        {
 6894            if (string.IsNullOrEmpty(scheme))
 895            {
 0896                throw Errors.ArgumentNull(nameof(scheme));
 897            }
 898
 6899            if (string.IsNullOrEmpty(accountName))
 900            {
 0901                throw Errors.ArgumentNull(nameof(accountName));
 902            }
 903
 6904            if (string.IsNullOrEmpty(endpointSuffix))
 905            {
 6906                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 907            }
 908
 6909            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>
 6917        private static (Uri, Uri) ConstructQueueEndpoint(IDictionary<string, string> settings) => ConstructQueueEndpoint
 6918                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 6919                settings[Constants.ConnectionStrings.AccountNameSetting],
 6920                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 6921                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        {
 6933            if (string.IsNullOrEmpty(scheme))
 934            {
 0935                throw Errors.ArgumentNull(nameof(scheme));
 936            }
 937
 6938            if (string.IsNullOrEmpty(accountName))
 939            {
 0940                throw Errors.ArgumentNull(nameof(accountName));
 941            }
 942
 6943            if (string.IsNullOrEmpty(endpointSuffix))
 944            {
 6945                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 946            }
 947
 6948            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>
 6956        private static (Uri, Uri) ConstructTableEndpoint(IDictionary<string, string> settings) => ConstructTableEndpoint
 6957                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 6958                settings[Constants.ConnectionStrings.AccountNameSetting],
 6959                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 6960                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        {
 6972            if (string.IsNullOrEmpty(scheme))
 973            {
 0974                throw Errors.ArgumentNull(nameof(scheme));
 975            }
 976
 6977            if (string.IsNullOrEmpty(accountName))
 978            {
 0979                throw Errors.ArgumentNull(nameof(accountName));
 980            }
 981
 6982            if (string.IsNullOrEmpty(endpointSuffix))
 983            {
 6984                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 985            }
 986
 6987            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        {
 201006            var primaryUriBuilder = new UriBuilder
 201007            {
 201008                Scheme = scheme,
 201009                Host = string.Format(
 201010                        CultureInfo.InvariantCulture,
 201011                        "{0}.{1}.{2}",
 201012                        accountName,
 201013                        hostNamePrefix,
 201014                        endpointSuffix),
 201015                Query = sasToken
 201016            };
 1017
 201018            var secondaryUriBuilder = new UriBuilder
 201019            {
 201020                Scheme = scheme,
 201021                Host = string.Format(
 201022                        CultureInfo.InvariantCulture,
 201023                        "{0}{1}.{2}.{3}",
 201024                        accountName,
 201025                        Constants.ConnectionStrings.SecondaryLocationAccountSuffix,
 201026                        hostNamePrefix,
 201027                        endpointSuffix),
 201028                Query = sasToken
 201029            };
 1030
 201031            return (primaryUriBuilder.Uri, secondaryUriBuilder.Uri);
 1032        }
 1033    }
 1034}