< Summary

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

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
get_UseV1MD5()-0%100%
.cctor()-79.62%73.91%
.ctor(...)-100%100%
get_DevelopmentStorageAccount()-0%0%
get_IsDevStoreAccount()-0%100%
get_EndpointSuffix()-100%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(...)-0%0%
GetDevelopmentStorageAccount(...)-0%0%
ParseCore(...)-88.57%67.65%
ParseStringIntoSettings(...)-66.67%66.67%
Setting(...)-100%100%
Setting(...)-100%100%
IsValidBase64String(...)-60%100%
IsValidUri(...)-100%100%
IsValidDomain(...)-100%100%
AllRequired(...)-100%100%
Optional(...)-100%100%
AtLeastOne(...)-100%100%
None(...)-100%100%
MatchesAll(...)-100%100%
MatchesOne(...)-100%50%
MatchesExactly(...)-100%100%
MatchesSpecification(...)-100%100%
GetCredentials(...)-100%80%
ConstructBlobEndpoint(...)-100%100%
ConstructBlobEndpoint(...)-71.43%66.67%
ConstructFileEndpoint(...)-100%100%
ConstructFileEndpoint(...)-71.43%66.67%
ConstructQueueEndpoint(...)-100%50%
ConstructQueueEndpoint(...)-71.43%66.67%
ConstructTableEndpoint(...)-100%100%
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.</
 40118        public StorageConnectionString(
 40119            object storageCredentials,
 40120            (Uri, Uri) blobStorageUri = default,
 40121            (Uri, Uri) queueStorageUri = default,
 40122            (Uri, Uri) tableStorageUri = default,
 40123            (Uri, Uri) fileStorageUri = default)
 124        {
 40125            Credentials = storageCredentials;
 40126            BlobStorageUri = blobStorageUri;
 40127            QueueStorageUri = queueStorageUri;
 40128            TableStorageUri = tableStorageUri;
 40129            FileStorageUri = fileStorageUri;
 40130            DefaultEndpoints = false;
 40131        }
 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>
 36159        internal string EndpointSuffix { get; set; }
 160
 161        /// <summary>
 162        /// The connection string parsed into settings.
 163        /// </summary>
 120164        internal IDictionary<string, string> Settings { get; set; }
 165
 166        /// <summary>
 167        /// True if the user used a constructor that auto-generates endpoints.
 168        /// </summary>
 60169        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>
 16175        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>
 48181        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>
 12193        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>
 72199        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>
 116205        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>
 68217        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>
 120223        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
 24242            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            {
 24249                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        {
 24368            IDictionary<string, string> settings = ParseStringIntoSettings(connectionString, error);
 369
 370            // malformed settings string
 371
 24372            if (settings == null)
 373            {
 0374                accountInformation = null;
 375
 0376                return false;
 377            }
 378
 379            // helper method
 380
 381            string settingOrDefault(string key)
 382            {
 264383                settings.TryGetValue(key, out var result);
 384
 264385                return result;
 386            }
 387
 388            // devstore case
 389
 24390            if (MatchesSpecification(
 24391                settings,
 24392                AllRequired(s_useDevelopmentStorageSetting),
 24393                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
 24408            ConnectionStringFilter endpointsOptional =
 24409                Optional(
 24410                    s_blobEndpointSetting, s_blobSecondaryEndpointSetting,
 24411                    s_queueEndpointSetting, s_queueSecondaryEndpointSetting,
 24412                    s_fileEndpointSetting, s_fileSecondaryEndpointSetting,
 24413                    s_tableEndpointSetting, s_tableSecondaryEndpointSetting // not supported but we don't want default c
 24414                    );
 415
 24416            ConnectionStringFilter primaryEndpointRequired =
 24417                AtLeastOne(
 24418                    s_blobEndpointSetting,
 24419                    s_queueEndpointSetting,
 24420                    s_fileEndpointSetting,
 24421                    s_tableEndpointSetting
 24422                    );
 423
 24424            ConnectionStringFilter secondaryEndpointsOptional =
 24425                Optional(
 24426                    s_blobSecondaryEndpointSetting,
 24427                    s_queueSecondaryEndpointSetting,
 24428                    s_fileSecondaryEndpointSetting,
 24429                    s_tableSecondaryEndpointSetting
 24430                    );
 431
 24432            ConnectionStringFilter automaticEndpointsMatchSpec =
 24433                MatchesExactly(MatchesAll(
 24434                    MatchesOne(
 24435                        MatchesAll(AllRequired(s_accountKeySetting), Optional(s_accountKeyNameSetting)), // Key + Name, 
 24436                        AllRequired(s_sharedAccessSignatureSetting) // SAS + Name, Endpoints optional
 24437                    ),
 24438                    AllRequired(s_accountNameSetting), // Name required to automatically create URIs
 24439                    endpointsOptional,
 24440                    Optional(s_defaultEndpointsProtocolSetting, s_endpointSuffixSetting)
 24441                    ));
 442
 24443            ConnectionStringFilter explicitEndpointsMatchSpec =
 24444                MatchesExactly(MatchesAll( // Any Credentials, Endpoints must be explicitly declared
 24445                    s_validCredentials,
 24446                    primaryEndpointRequired,
 24447                    secondaryEndpointsOptional
 24448                    ));
 449
 24450            var matchesAutomaticEndpointsSpec = MatchesSpecification(settings, automaticEndpointsMatchSpec);
 24451            var matchesExplicitEndpointsSpec = MatchesSpecification(settings, explicitEndpointsMatchSpec);
 452
 24453            if (matchesAutomaticEndpointsSpec || matchesExplicitEndpointsSpec)
 454            {
 24455                if (matchesAutomaticEndpointsSpec && !settings.ContainsKey(Constants.ConnectionStrings.DefaultEndpointsP
 456                {
 16457                    settings.Add(Constants.ConnectionStrings.DefaultEndpointsProtocolSetting, "https");
 458                }
 459
 24460                var blobEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobEndpointSetting);
 24461                var queueEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueEndpointSetting);
 24462                var tableEndpoint = settingOrDefault(Constants.ConnectionStrings.TableEndpointSetting);
 24463                var fileEndpoint = settingOrDefault(Constants.ConnectionStrings.FileEndpointSetting);
 24464                var blobSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobSecondaryEndpointSetting);
 24465                var queueSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueSecondaryEndpointSetting)
 24466                var tableSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.TableSecondaryEndpointSetting)
 24467                var fileSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.FileSecondaryEndpointSetting);
 24468                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) =>
 96473                        !string.IsNullOrWhiteSpace(primary)
 96474                        || /* primary is null, and... */ string.IsNullOrWhiteSpace(secondary);
 475
 476                (Uri, Uri) createStorageUri(string primary, string secondary, string sasToken, Func<IDictionary<string, 
 477                {
 96478                    return
 96479                        !string.IsNullOrWhiteSpace(secondary) && !string.IsNullOrWhiteSpace(primary)
 96480                            ? (CreateUri(primary, sasToken), CreateUri(secondary, sasToken))
 96481                        : !string.IsNullOrWhiteSpace(primary)
 96482                            ? (CreateUri(primary, sasToken), default)
 96483                        : matchesAutomaticEndpointsSpec && factory != null
 96484                            ? factory(settings)
 96485                        : (default, default);
 486
 487                    static Uri CreateUri(string endpoint, string sasToken)
 488                    {
 40489                        var builder = new UriBuilder(endpoint);
 40490                        if (!string.IsNullOrEmpty(builder.Query))
 491                        {
 0492                            builder.Query += "&" + sasToken;
 493                        }
 494                        else
 495                        {
 40496                            builder.Query = sasToken;
 497                        }
 40498                        return builder.Uri;
 499                    }
 500                }
 501
 24502                if (
 24503                    s_isValidEndpointPair(blobEndpoint, blobSecondaryEndpoint)
 24504                    && s_isValidEndpointPair(queueEndpoint, queueSecondaryEndpoint)
 24505                    && s_isValidEndpointPair(tableEndpoint, tableSecondaryEndpoint)
 24506                    && s_isValidEndpointPair(fileEndpoint, fileSecondaryEndpoint)
 24507                    )
 508                {
 24509                    accountInformation =
 24510                        new StorageConnectionString(
 24511                            GetCredentials(settings),
 24512                            blobStorageUri: createStorageUri(blobEndpoint, blobSecondaryEndpoint, sasToken, ConstructBlo
 24513                            queueStorageUri: createStorageUri(queueEndpoint, queueSecondaryEndpoint, sasToken, Construct
 24514                            tableStorageUri: createStorageUri(tableEndpoint, tableSecondaryEndpoint, sasToken, Construct
 24515                            fileStorageUri: createStorageUri(fileEndpoint, fileSecondaryEndpoint, sasToken, ConstructFil
 24516                            )
 24517                        {
 24518                            EndpointSuffix = settingOrDefault(Constants.ConnectionStrings.EndpointSuffixSetting),
 24519                            Settings = s_validCredentials(settings)
 24520                        };
 521
 24522                    accountInformation._accountName = settingOrDefault(Constants.ConnectionStrings.AccountNameSetting);
 523
 24524                    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        {
 24544            IDictionary<string, string> settings = new Dictionary<string, string>();
 24545            var splitted = connectionString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
 546
 232547            foreach (var nameValue in splitted)
 548            {
 92549                var splittedNameValue = nameValue.Split(new char[] { '=' }, 2);
 550
 92551                if (splittedNameValue.Length != 2)
 552                {
 0553                    error("Settings must be of the form \"name=value\".");
 0554                    return null;
 555                }
 556
 92557                if (settings.ContainsKey(splittedNameValue[0]))
 558                {
 0559                    error(string.Format(CultureInfo.InvariantCulture, "Duplicate setting '{0}' found.", splittedNameValu
 0560                    return null;
 561                }
 562
 92563                settings.Add(splittedNameValue[0], splittedNameValue[1]);
 564            }
 565
 24566            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,
 162578                (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            {
 80598                Convert.FromBase64String(settingValue);
 599
 80600                return true;
 601            }
 0602            catch (FormatException)
 603            {
 0604                return false;
 605            }
 80606        }
 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>
 72613        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>
 4620        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) =>
 100628            (settings) =>
 100629            {
 292630                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 100631
 848632                foreach (AccountSetting requirement in requiredSettings)
 100633                {
 332634                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 100635                    {
 232636                        result.Remove(requirement.Key);
 100637                    }
 100638                    else
 100639                    {
 200640                        return null;
 100641                    }
 100642                }
 100643
 192644                return result;
 100645            };
 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) =>
 124653            (settings) =>
 124654            {
 248655                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 124656
 1060657                foreach (AccountSetting requirement in optionalSettings)
 124658                {
 468659                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 124660                    {
 192661                        result.Remove(requirement.Key);
 124662                    }
 124663                }
 124664
 248665                return result;
 124666            };
 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) =>
 24675            (settings) =>
 24676            {
 48677                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 48678                var foundOne = false;
 24679
 264680                foreach (AccountSetting requirement in atLeastOneSettings)
 24681                {
 120682                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 24683                    {
 44684                        result.Remove(requirement.Key);
 44685                        foundOne = true;
 24686                    }
 24687                }
 24688
 48689                return foundOne ? result : null;
 24690            };
 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            {
 102701                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 102702                var foundOne = false;
 6703
 710704                foreach (AccountSetting requirement in atLeastOneSettings)
 6705                {
 262706                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 6707                    {
 94708                        foundOne = true;
 6709                    }
 6710                }
 6711
 102712                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) =>
 76721            (settings) =>
 76722            {
 244723                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 76724
 1252725                foreach (ConnectionStringFilter filter in filters)
 76726                {
 528727                    if (result == null)
 76728                    {
 76729                        break;
 76730                    }
 76731
 464732                    result = filter(result);
 76733                }
 76734
 244735                return result;
 76736            };
 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) =>
 26744            (settings) =>
 26745            {
 98746                IDictionary<string, string>[] results =
 98747                    filters
 290748                    .Select(filter => filter(new Dictionary<string, string>(settings)))
 290749                    .Where(result => result != null)
 98750                    .Take(2)
 98751                    .ToArray();
 26752
 98753                return results.Length != 1 ? null : results.First();
 26754            };
 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) =>
 48762            (settings) =>
 48763            {
 96764                IDictionary<string, string> results = filter(settings);
 48765
 96766                return results == null || results.Any() ? null : results;
 48767            };
 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        {
 256794            foreach (ConnectionStringFilter constraint in constraints)
 795            {
 72796                IDictionary<string, string> remainingSettings = constraint(settings);
 797
 72798                if (remainingSettings == null)
 799                {
 32800                    return false;
 801                }
 802                else
 803                {
 40804                    settings = remainingSettings;
 805                }
 806            }
 807
 40808            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
 24819            settings.TryGetValue(Constants.ConnectionStrings.AccountNameSetting, out var accountName);
 24820            settings.TryGetValue(Constants.ConnectionStrings.AccountKeySetting, out var accountKey);
 24821            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
 24826            return
 24827                accountName != null && accountKey != null && sharedAccessSignature == null
 24828                ? new StorageSharedKeyCredential(accountName, accountKey/*, accountKeyName */)
 24829                : (object)(accountKey == null /* && accountKeyName == null */ && sharedAccessSignature != null
 24830                    ? new SharedAccessSignatureCredentials(sharedAccessSignature)
 24831                    : 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>
 20839        private static (Uri, Uri) ConstructBlobEndpoint(IDictionary<string, string> settings) => ConstructBlobEndpoint(
 20840                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 20841                settings[Constants.ConnectionStrings.AccountNameSetting],
 20842                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 20843                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        {
 24855            if (string.IsNullOrEmpty(scheme))
 856            {
 0857                throw Errors.ArgumentNull(nameof(scheme));
 858            }
 859
 24860            if (string.IsNullOrEmpty(accountName))
 861            {
 0862                throw Errors.ArgumentNull(nameof(accountName));
 863            }
 864
 24865            if (string.IsNullOrEmpty(endpointSuffix))
 866            {
 16867                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 868            }
 869
 24870            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>
 20878        private static (Uri, Uri) ConstructFileEndpoint(IDictionary<string, string> settings) => ConstructFileEndpoint(
 20879                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 20880                settings[Constants.ConnectionStrings.AccountNameSetting],
 20881                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 20882                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        {
 24894            if (string.IsNullOrEmpty(scheme))
 895            {
 0896                throw Errors.ArgumentNull(nameof(scheme));
 897            }
 898
 24899            if (string.IsNullOrEmpty(accountName))
 900            {
 0901                throw Errors.ArgumentNull(nameof(accountName));
 902            }
 903
 24904            if (string.IsNullOrEmpty(endpointSuffix))
 905            {
 16906                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 907            }
 908
 24909            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>
 4917        private static (Uri, Uri) ConstructQueueEndpoint(IDictionary<string, string> settings) => ConstructQueueEndpoint
 4918                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 4919                settings[Constants.ConnectionStrings.AccountNameSetting],
 4920                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 4921                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        {
 12933            if (string.IsNullOrEmpty(scheme))
 934            {
 0935                throw Errors.ArgumentNull(nameof(scheme));
 936            }
 937
 12938            if (string.IsNullOrEmpty(accountName))
 939            {
 0940                throw Errors.ArgumentNull(nameof(accountName));
 941            }
 942
 12943            if (string.IsNullOrEmpty(endpointSuffix))
 944            {
 4945                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 946            }
 947
 12948            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>
 20956        private static (Uri, Uri) ConstructTableEndpoint(IDictionary<string, string> settings) => ConstructTableEndpoint
 20957                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 20958                settings[Constants.ConnectionStrings.AccountNameSetting],
 20959                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 20960                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        {
 20972            if (string.IsNullOrEmpty(scheme))
 973            {
 0974                throw Errors.ArgumentNull(nameof(scheme));
 975            }
 976
 20977            if (string.IsNullOrEmpty(accountName))
 978            {
 0979                throw Errors.ArgumentNull(nameof(accountName));
 980            }
 981
 20982            if (string.IsNullOrEmpty(endpointSuffix))
 983            {
 16984                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 985            }
 986
 20987            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        {
 801006            var primaryUriBuilder = new UriBuilder
 801007            {
 801008                Scheme = scheme,
 801009                Host = string.Format(
 801010                        CultureInfo.InvariantCulture,
 801011                        "{0}.{1}.{2}",
 801012                        accountName,
 801013                        hostNamePrefix,
 801014                        endpointSuffix),
 801015                Query = sasToken
 801016            };
 1017
 801018            var secondaryUriBuilder = new UriBuilder
 801019            {
 801020                Scheme = scheme,
 801021                Host = string.Format(
 801022                        CultureInfo.InvariantCulture,
 801023                        "{0}{1}.{2}.{3}",
 801024                        accountName,
 801025                        Constants.ConnectionStrings.SecondaryLocationAccountSuffix,
 801026                        hostNamePrefix,
 801027                        endpointSuffix),
 801028                Query = sasToken
 801029            };
 1030
 801031            return (primaryUriBuilder.Uri, secondaryUriBuilder.Uri);
 1032        }
 1033    }
 1034}