< Summary

Class:Azure.Storage.StorageConnectionString
Assembly:Azure.Storage.Blobs
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%50%
ConstructBlobEndpoint(...)-71.43%66.67%
ConstructFileEndpoint(...)-100%100%
ConstructFileEndpoint(...)-71.43%66.67%
ConstructQueueEndpoint(...)-100%100%
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.</
 216118        public StorageConnectionString(
 216119            object storageCredentials,
 216120            (Uri, Uri) blobStorageUri = default,
 216121            (Uri, Uri) queueStorageUri = default,
 216122            (Uri, Uri) tableStorageUri = default,
 216123            (Uri, Uri) fileStorageUri = default)
 124        {
 216125            Credentials = storageCredentials;
 216126            BlobStorageUri = blobStorageUri;
 216127            QueueStorageUri = queueStorageUri;
 216128            TableStorageUri = tableStorageUri;
 216129            FileStorageUri = fileStorageUri;
 216130            DefaultEndpoints = false;
 216131        }
 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>
 180159        internal string EndpointSuffix { get; set; }
 160
 161        /// <summary>
 162        /// The connection string parsed into settings.
 163        /// </summary>
 480164        internal IDictionary<string, string> Settings { get; set; }
 165
 166        /// <summary>
 167        /// True if the user used a constructor that auto-generates endpoints.
 168        /// </summary>
 284169        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>
 268175        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>
 52181        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>
 52193        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>
 596199        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>
 328205        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>
 328217        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>
 532223        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
 156242            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            {
 156249                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        {
 156368            IDictionary<string, string> settings = ParseStringIntoSettings(connectionString, error);
 369
 370            // malformed settings string
 371
 156372            if (settings == null)
 373            {
 0374                accountInformation = null;
 375
 0376                return false;
 377            }
 378
 379            // helper method
 380
 381            string settingOrDefault(string key)
 382            {
 1716383                settings.TryGetValue(key, out var result);
 384
 1716385                return result;
 386            }
 387
 388            // devstore case
 389
 156390            if (MatchesSpecification(
 156391                settings,
 156392                AllRequired(s_useDevelopmentStorageSetting),
 156393                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
 156408            ConnectionStringFilter endpointsOptional =
 156409                Optional(
 156410                    s_blobEndpointSetting, s_blobSecondaryEndpointSetting,
 156411                    s_queueEndpointSetting, s_queueSecondaryEndpointSetting,
 156412                    s_fileEndpointSetting, s_fileSecondaryEndpointSetting,
 156413                    s_tableEndpointSetting, s_tableSecondaryEndpointSetting // not supported but we don't want default c
 156414                    );
 415
 156416            ConnectionStringFilter primaryEndpointRequired =
 156417                AtLeastOne(
 156418                    s_blobEndpointSetting,
 156419                    s_queueEndpointSetting,
 156420                    s_fileEndpointSetting,
 156421                    s_tableEndpointSetting
 156422                    );
 423
 156424            ConnectionStringFilter secondaryEndpointsOptional =
 156425                Optional(
 156426                    s_blobSecondaryEndpointSetting,
 156427                    s_queueSecondaryEndpointSetting,
 156428                    s_fileSecondaryEndpointSetting,
 156429                    s_tableSecondaryEndpointSetting
 156430                    );
 431
 156432            ConnectionStringFilter automaticEndpointsMatchSpec =
 156433                MatchesExactly(MatchesAll(
 156434                    MatchesOne(
 156435                        MatchesAll(AllRequired(s_accountKeySetting), Optional(s_accountKeyNameSetting)), // Key + Name, 
 156436                        AllRequired(s_sharedAccessSignatureSetting) // SAS + Name, Endpoints optional
 156437                    ),
 156438                    AllRequired(s_accountNameSetting), // Name required to automatically create URIs
 156439                    endpointsOptional,
 156440                    Optional(s_defaultEndpointsProtocolSetting, s_endpointSuffixSetting)
 156441                    ));
 442
 156443            ConnectionStringFilter explicitEndpointsMatchSpec =
 156444                MatchesExactly(MatchesAll( // Any Credentials, Endpoints must be explicitly declared
 156445                    s_validCredentials,
 156446                    primaryEndpointRequired,
 156447                    secondaryEndpointsOptional
 156448                    ));
 449
 156450            var matchesAutomaticEndpointsSpec = MatchesSpecification(settings, automaticEndpointsMatchSpec);
 156451            var matchesExplicitEndpointsSpec = MatchesSpecification(settings, explicitEndpointsMatchSpec);
 452
 156453            if (matchesAutomaticEndpointsSpec || matchesExplicitEndpointsSpec)
 454            {
 156455                if (matchesAutomaticEndpointsSpec && !settings.ContainsKey(Constants.ConnectionStrings.DefaultEndpointsP
 456                {
 124457                    settings.Add(Constants.ConnectionStrings.DefaultEndpointsProtocolSetting, "https");
 458                }
 459
 156460                var blobEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobEndpointSetting);
 156461                var queueEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueEndpointSetting);
 156462                var tableEndpoint = settingOrDefault(Constants.ConnectionStrings.TableEndpointSetting);
 156463                var fileEndpoint = settingOrDefault(Constants.ConnectionStrings.FileEndpointSetting);
 156464                var blobSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobSecondaryEndpointSetting);
 156465                var queueSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueSecondaryEndpointSetting)
 156466                var tableSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.TableSecondaryEndpointSetting)
 156467                var fileSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.FileSecondaryEndpointSetting);
 156468                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) =>
 624473                        !string.IsNullOrWhiteSpace(primary)
 624474                        || /* primary is null, and... */ string.IsNullOrWhiteSpace(secondary);
 475
 476                (Uri, Uri) createStorageUri(string primary, string secondary, string sasToken, Func<IDictionary<string, 
 477                {
 624478                    return
 624479                        !string.IsNullOrWhiteSpace(secondary) && !string.IsNullOrWhiteSpace(primary)
 624480                            ? (CreateUri(primary, sasToken), CreateUri(secondary, sasToken))
 624481                        : !string.IsNullOrWhiteSpace(primary)
 624482                            ? (CreateUri(primary, sasToken), default)
 624483                        : matchesAutomaticEndpointsSpec && factory != null
 624484                            ? factory(settings)
 624485                        : (default, default);
 486
 487                    static Uri CreateUri(string endpoint, string sasToken)
 488                    {
 632489                        var builder = new UriBuilder(endpoint);
 632490                        if (!string.IsNullOrEmpty(builder.Query))
 491                        {
 0492                            builder.Query += "&" + sasToken;
 493                        }
 494                        else
 495                        {
 632496                            builder.Query = sasToken;
 497                        }
 632498                        return builder.Uri;
 499                    }
 500                }
 501
 156502                if (
 156503                    s_isValidEndpointPair(blobEndpoint, blobSecondaryEndpoint)
 156504                    && s_isValidEndpointPair(queueEndpoint, queueSecondaryEndpoint)
 156505                    && s_isValidEndpointPair(tableEndpoint, tableSecondaryEndpoint)
 156506                    && s_isValidEndpointPair(fileEndpoint, fileSecondaryEndpoint)
 156507                    )
 508                {
 156509                    accountInformation =
 156510                        new StorageConnectionString(
 156511                            GetCredentials(settings),
 156512                            blobStorageUri: createStorageUri(blobEndpoint, blobSecondaryEndpoint, sasToken, ConstructBlo
 156513                            queueStorageUri: createStorageUri(queueEndpoint, queueSecondaryEndpoint, sasToken, Construct
 156514                            tableStorageUri: createStorageUri(tableEndpoint, tableSecondaryEndpoint, sasToken, Construct
 156515                            fileStorageUri: createStorageUri(fileEndpoint, fileSecondaryEndpoint, sasToken, ConstructFil
 156516                            )
 156517                        {
 156518                            EndpointSuffix = settingOrDefault(Constants.ConnectionStrings.EndpointSuffixSetting),
 156519                            Settings = s_validCredentials(settings)
 156520                        };
 521
 156522                    accountInformation._accountName = settingOrDefault(Constants.ConnectionStrings.AccountNameSetting);
 523
 156524                    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        {
 156544            IDictionary<string, string> settings = new Dictionary<string, string>();
 156545            var splitted = connectionString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
 546
 2184547            foreach (var nameValue in splitted)
 548            {
 936549                var splittedNameValue = nameValue.Split(new char[] { '=' }, 2);
 550
 936551                if (splittedNameValue.Length != 2)
 552                {
 0553                    error("Settings must be of the form \"name=value\".");
 0554                    return null;
 555                }
 556
 936557                if (settings.ContainsKey(splittedNameValue[0]))
 558                {
 0559                    error(string.Format(CultureInfo.InvariantCulture, "Duplicate setting '{0}' found.", splittedNameValu
 0560                    return null;
 561                }
 562
 936563                settings.Add(splittedNameValue[0], splittedNameValue[1]);
 564            }
 565
 156566            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,
 854578                (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            {
 620598                Convert.FromBase64String(settingValue);
 599
 620600                return true;
 601            }
 0602            catch (FormatException)
 603            {
 0604                return false;
 605            }
 620606        }
 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>
 1216613        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>
 8620        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) =>
 628628            (settings) =>
 628629            {
 1876630                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 628631
 5500632                foreach (AccountSetting requirement in requiredSettings)
 628633                {
 2140634                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 628635                    {
 1492636                        result.Remove(requirement.Key);
 628637                    }
 628638                    else
 628639                    {
 1276640                        return null;
 628641                    }
 628642                }
 628643
 1228644                return result;
 628645            };
 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) =>
 784653            (settings) =>
 784654            {
 1632655                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 784656
 7176657                foreach (AccountSetting requirement in optionalSettings)
 784658                {
 3132659                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 784660                    {
 1716661                        result.Remove(requirement.Key);
 784662                    }
 784663                }
 784664
 1632665                return result;
 784666            };
 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) =>
 156675            (settings) =>
 156676            {
 312677                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 312678                var foundOne = false;
 156679
 1716680                foreach (AccountSetting requirement in atLeastOneSettings)
 156681                {
 780682                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 156683                    {
 472684                        result.Remove(requirement.Key);
 472685                        foundOne = true;
 156686                    }
 156687                }
 156688
 312689                return foundOne ? result : null;
 156690            };
 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            {
 630701                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 630702                var foundOne = false;
 6703
 4502704                foreach (AccountSetting requirement in atLeastOneSettings)
 6705                {
 1630706                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 6707                    {
 582708                        foundOne = true;
 6709                    }
 6710                }
 6711
 630712                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) =>
 472721            (settings) =>
 472722            {
 1564723                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 472724
 8160725                foreach (ConnectionStringFilter filter in filters)
 472726                {
 3412727                    if (result == null)
 472728                    {
 472729                        break;
 472730                    }
 472731
 3036732                    result = filter(result);
 472733                }
 472734
 1564735                return result;
 472736            };
 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) =>
 158744            (settings) =>
 158745            {
 626746                IDictionary<string, string>[] results =
 626747                    filters
 1874748                    .Select(filter => filter(new Dictionary<string, string>(settings)))
 1874749                    .Where(result => result != null)
 626750                    .Take(2)
 626751                    .ToArray();
 158752
 626753                return results.Length != 1 ? null : results.First();
 158754            };
 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) =>
 312762            (settings) =>
 312763            {
 624764                IDictionary<string, string> results = filter(settings);
 312765
 624766                return results == null || results.Any() ? null : results;
 312767            };
 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        {
 1684794            foreach (ConnectionStringFilter constraint in constraints)
 795            {
 468796                IDictionary<string, string> remainingSettings = constraint(settings);
 797
 468798                if (remainingSettings == null)
 799                {
 188800                    return false;
 801                }
 802                else
 803                {
 280804                    settings = remainingSettings;
 805                }
 806            }
 807
 280808            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
 156819            settings.TryGetValue(Constants.ConnectionStrings.AccountNameSetting, out var accountName);
 156820            settings.TryGetValue(Constants.ConnectionStrings.AccountKeySetting, out var accountKey);
 156821            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
 156826            return
 156827                accountName != null && accountKey != null && sharedAccessSignature == null
 156828                ? new StorageSharedKeyCredential(accountName, accountKey/*, accountKeyName */)
 156829                : (object)(accountKey == null /* && accountKeyName == null */ && sharedAccessSignature != null
 156830                    ? new SharedAccessSignatureCredentials(sharedAccessSignature)
 156831                    : 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>
 8839        private static (Uri, Uri) ConstructBlobEndpoint(IDictionary<string, string> settings) => ConstructBlobEndpoint(
 8840                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 8841                settings[Constants.ConnectionStrings.AccountNameSetting],
 8842                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 8843                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        {
 40855            if (string.IsNullOrEmpty(scheme))
 856            {
 0857                throw Errors.ArgumentNull(nameof(scheme));
 858            }
 859
 40860            if (string.IsNullOrEmpty(accountName))
 861            {
 0862                throw Errors.ArgumentNull(nameof(accountName));
 863            }
 864
 40865            if (string.IsNullOrEmpty(endpointSuffix))
 866            {
 24867                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 868            }
 869
 40870            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>
 48878        private static (Uri, Uri) ConstructFileEndpoint(IDictionary<string, string> settings) => ConstructFileEndpoint(
 48879                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 48880                settings[Constants.ConnectionStrings.AccountNameSetting],
 48881                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 48882                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        {
 56894            if (string.IsNullOrEmpty(scheme))
 895            {
 0896                throw Errors.ArgumentNull(nameof(scheme));
 897            }
 898
 56899            if (string.IsNullOrEmpty(accountName))
 900            {
 0901                throw Errors.ArgumentNull(nameof(accountName));
 902            }
 903
 56904            if (string.IsNullOrEmpty(endpointSuffix))
 905            {
 40906                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 907            }
 908
 56909            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>
 48917        private static (Uri, Uri) ConstructQueueEndpoint(IDictionary<string, string> settings) => ConstructQueueEndpoint
 48918                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 48919                settings[Constants.ConnectionStrings.AccountNameSetting],
 48920                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 48921                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        {
 56933            if (string.IsNullOrEmpty(scheme))
 934            {
 0935                throw Errors.ArgumentNull(nameof(scheme));
 936            }
 937
 56938            if (string.IsNullOrEmpty(accountName))
 939            {
 0940                throw Errors.ArgumentNull(nameof(accountName));
 941            }
 942
 56943            if (string.IsNullOrEmpty(endpointSuffix))
 944            {
 40945                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 946            }
 947
 56948            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>
 132956        private static (Uri, Uri) ConstructTableEndpoint(IDictionary<string, string> settings) => ConstructTableEndpoint
 132957                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 132958                settings[Constants.ConnectionStrings.AccountNameSetting],
 132959                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 132960                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        {
 136972            if (string.IsNullOrEmpty(scheme))
 973            {
 0974                throw Errors.ArgumentNull(nameof(scheme));
 975            }
 976
 136977            if (string.IsNullOrEmpty(accountName))
 978            {
 0979                throw Errors.ArgumentNull(nameof(accountName));
 980            }
 981
 136982            if (string.IsNullOrEmpty(endpointSuffix))
 983            {
 128984                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 985            }
 986
 136987            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        {
 2881006            var primaryUriBuilder = new UriBuilder
 2881007            {
 2881008                Scheme = scheme,
 2881009                Host = string.Format(
 2881010                        CultureInfo.InvariantCulture,
 2881011                        "{0}.{1}.{2}",
 2881012                        accountName,
 2881013                        hostNamePrefix,
 2881014                        endpointSuffix),
 2881015                Query = sasToken
 2881016            };
 1017
 2881018            var secondaryUriBuilder = new UriBuilder
 2881019            {
 2881020                Scheme = scheme,
 2881021                Host = string.Format(
 2881022                        CultureInfo.InvariantCulture,
 2881023                        "{0}{1}.{2}.{3}",
 2881024                        accountName,
 2881025                        Constants.ConnectionStrings.SecondaryLocationAccountSuffix,
 2881026                        hostNamePrefix,
 2881027                        endpointSuffix),
 2881028                Query = sasToken
 2881029            };
 1030
 2881031            return (primaryUriBuilder.Uri, secondaryUriBuilder.Uri);
 1032        }
 1033    }
 1034}