< Summary

Class:Azure.Storage.StorageConnectionString
Assembly:Azure.Storage.Files.Shares
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:112
Total branches:146
Branch coverage:76.7% (112 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%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>
 127        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>
 132        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>
 137        private static readonly AccountSetting s_defaultEndpointsProtocolSetting = Setting(Constants.ConnectionStrings.D
 38
 39        /// <summary>
 40        /// Validator for the AccountName setting. No restrictions.
 41        /// </summary>
 142        private static readonly AccountSetting s_accountNameSetting = Setting(Constants.ConnectionStrings.AccountNameSet
 43
 44        /// <summary>
 45        /// Validator for the AccountKey setting. No restrictions.
 46        /// </summary>
 147        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>
 152        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>
 157        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>
 162        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>
 167        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>
 172        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>
 177        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>
 182        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>
 187        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>
 192        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>
 197        private static readonly AccountSetting s_endpointSuffixSetting = Setting(Constants.ConnectionStrings.EndpointSuf
 98
 99        /// <summary>
 100        /// Validator for the SharedAccessSignature setting. No restrictions.
 101        /// </summary>
 1102        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.</
 66118        public StorageConnectionString(
 66119            object storageCredentials,
 66120            (Uri, Uri) blobStorageUri = default,
 66121            (Uri, Uri) queueStorageUri = default,
 66122            (Uri, Uri) tableStorageUri = default,
 66123            (Uri, Uri) fileStorageUri = default)
 124        {
 66125            Credentials = storageCredentials;
 66126            BlobStorageUri = blobStorageUri;
 66127            QueueStorageUri = queueStorageUri;
 66128            TableStorageUri = tableStorageUri;
 66129            FileStorageUri = fileStorageUri;
 66130            DefaultEndpoints = false;
 66131        }
 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>
 60159        internal string EndpointSuffix { get; set; }
 160
 161        /// <summary>
 162        /// The connection string parsed into settings.
 163        /// </summary>
 106164        internal IDictionary<string, string> Settings { get; set; }
 165
 166        /// <summary>
 167        /// True if the user used a constructor that auto-generates endpoints.
 168        /// </summary>
 80169        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>
 12175        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>
 10181        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>
 70193        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>
 90199        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>
 88205        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>
 154217        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>
 148223        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
 54242            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            {
 54249                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        {
 54368            IDictionary<string, string> settings = ParseStringIntoSettings(connectionString, error);
 369
 370            // malformed settings string
 371
 54372            if (settings == null)
 373            {
 0374                accountInformation = null;
 375
 0376                return false;
 377            }
 378
 379            // helper method
 380
 381            string settingOrDefault(string key)
 382            {
 594383                settings.TryGetValue(key, out var result);
 384
 594385                return result;
 386            }
 387
 388            // devstore case
 389
 54390            if (MatchesSpecification(
 54391                settings,
 54392                AllRequired(s_useDevelopmentStorageSetting),
 54393                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
 54408            ConnectionStringFilter endpointsOptional =
 54409                Optional(
 54410                    s_blobEndpointSetting, s_blobSecondaryEndpointSetting,
 54411                    s_queueEndpointSetting, s_queueSecondaryEndpointSetting,
 54412                    s_fileEndpointSetting, s_fileSecondaryEndpointSetting,
 54413                    s_tableEndpointSetting, s_tableSecondaryEndpointSetting // not supported but we don't want default c
 54414                    );
 415
 54416            ConnectionStringFilter primaryEndpointRequired =
 54417                AtLeastOne(
 54418                    s_blobEndpointSetting,
 54419                    s_queueEndpointSetting,
 54420                    s_fileEndpointSetting,
 54421                    s_tableEndpointSetting
 54422                    );
 423
 54424            ConnectionStringFilter secondaryEndpointsOptional =
 54425                Optional(
 54426                    s_blobSecondaryEndpointSetting,
 54427                    s_queueSecondaryEndpointSetting,
 54428                    s_fileSecondaryEndpointSetting,
 54429                    s_tableSecondaryEndpointSetting
 54430                    );
 431
 54432            ConnectionStringFilter automaticEndpointsMatchSpec =
 54433                MatchesExactly(MatchesAll(
 54434                    MatchesOne(
 54435                        MatchesAll(AllRequired(s_accountKeySetting), Optional(s_accountKeyNameSetting)), // Key + Name, 
 54436                        AllRequired(s_sharedAccessSignatureSetting) // SAS + Name, Endpoints optional
 54437                    ),
 54438                    AllRequired(s_accountNameSetting), // Name required to automatically create URIs
 54439                    endpointsOptional,
 54440                    Optional(s_defaultEndpointsProtocolSetting, s_endpointSuffixSetting)
 54441                    ));
 442
 54443            ConnectionStringFilter explicitEndpointsMatchSpec =
 54444                MatchesExactly(MatchesAll( // Any Credentials, Endpoints must be explicitly declared
 54445                    s_validCredentials,
 54446                    primaryEndpointRequired,
 54447                    secondaryEndpointsOptional
 54448                    ));
 449
 54450            var matchesAutomaticEndpointsSpec = MatchesSpecification(settings, automaticEndpointsMatchSpec);
 54451            var matchesExplicitEndpointsSpec = MatchesSpecification(settings, explicitEndpointsMatchSpec);
 452
 54453            if (matchesAutomaticEndpointsSpec || matchesExplicitEndpointsSpec)
 454            {
 54455                if (matchesAutomaticEndpointsSpec && !settings.ContainsKey(Constants.ConnectionStrings.DefaultEndpointsP
 456                {
 50457                    settings.Add(Constants.ConnectionStrings.DefaultEndpointsProtocolSetting, "https");
 458                }
 459
 54460                var blobEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobEndpointSetting);
 54461                var queueEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueEndpointSetting);
 54462                var tableEndpoint = settingOrDefault(Constants.ConnectionStrings.TableEndpointSetting);
 54463                var fileEndpoint = settingOrDefault(Constants.ConnectionStrings.FileEndpointSetting);
 54464                var blobSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.BlobSecondaryEndpointSetting);
 54465                var queueSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.QueueSecondaryEndpointSetting)
 54466                var tableSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.TableSecondaryEndpointSetting)
 54467                var fileSecondaryEndpoint = settingOrDefault(Constants.ConnectionStrings.FileSecondaryEndpointSetting);
 54468                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) =>
 216473                        !string.IsNullOrWhiteSpace(primary)
 216474                        || /* primary is null, and... */ string.IsNullOrWhiteSpace(secondary);
 475
 476                (Uri, Uri) createStorageUri(string primary, string secondary, string sasToken, Func<IDictionary<string, 
 477                {
 216478                    return
 216479                        !string.IsNullOrWhiteSpace(secondary) && !string.IsNullOrWhiteSpace(primary)
 216480                            ? (CreateUri(primary, sasToken), CreateUri(secondary, sasToken))
 216481                        : !string.IsNullOrWhiteSpace(primary)
 216482                            ? (CreateUri(primary, sasToken), default)
 216483                        : matchesAutomaticEndpointsSpec && factory != null
 216484                            ? factory(settings)
 216485                        : (default, default);
 486
 487                    static Uri CreateUri(string endpoint, string sasToken)
 488                    {
 264489                        var builder = new UriBuilder(endpoint);
 264490                        if (!string.IsNullOrEmpty(builder.Query))
 491                        {
 0492                            builder.Query += "&" + sasToken;
 493                        }
 494                        else
 495                        {
 264496                            builder.Query = sasToken;
 497                        }
 264498                        return builder.Uri;
 499                    }
 500                }
 501
 54502                if (
 54503                    s_isValidEndpointPair(blobEndpoint, blobSecondaryEndpoint)
 54504                    && s_isValidEndpointPair(queueEndpoint, queueSecondaryEndpoint)
 54505                    && s_isValidEndpointPair(tableEndpoint, tableSecondaryEndpoint)
 54506                    && s_isValidEndpointPair(fileEndpoint, fileSecondaryEndpoint)
 54507                    )
 508                {
 54509                    accountInformation =
 54510                        new StorageConnectionString(
 54511                            GetCredentials(settings),
 54512                            blobStorageUri: createStorageUri(blobEndpoint, blobSecondaryEndpoint, sasToken, ConstructBlo
 54513                            queueStorageUri: createStorageUri(queueEndpoint, queueSecondaryEndpoint, sasToken, Construct
 54514                            tableStorageUri: createStorageUri(tableEndpoint, tableSecondaryEndpoint, sasToken, Construct
 54515                            fileStorageUri: createStorageUri(fileEndpoint, fileSecondaryEndpoint, sasToken, ConstructFil
 54516                            )
 54517                        {
 54518                            EndpointSuffix = settingOrDefault(Constants.ConnectionStrings.EndpointSuffixSetting),
 54519                            Settings = s_validCredentials(settings)
 54520                        };
 521
 54522                    accountInformation._accountName = settingOrDefault(Constants.ConnectionStrings.AccountNameSetting);
 523
 54524                    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        {
 54544            IDictionary<string, string> settings = new Dictionary<string, string>();
 54545            var splitted = connectionString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
 546
 856547            foreach (var nameValue in splitted)
 548            {
 374549                var splittedNameValue = nameValue.Split(new char[] { '=' }, 2);
 550
 374551                if (splittedNameValue.Length != 2)
 552                {
 0553                    error("Settings must be of the form \"name=value\".");
 0554                    return null;
 555                }
 556
 374557                if (settings.ContainsKey(splittedNameValue[0]))
 558                {
 0559                    error(string.Format(CultureInfo.InvariantCulture, "Duplicate setting '{0}' found.", splittedNameValu
 0560                    return null;
 561                }
 562
 374563                settings.Add(splittedNameValue[0], splittedNameValue[1]);
 564            }
 565
 54566            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) =>
 5576            new AccountSetting(
 5577                name,
 291578                (settingValue) => validValues.Length == 0 ? true : validValues.Contains(settingValue)
 5579                );
 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>
 11587        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            {
 250598                Convert.FromBase64String(settingValue);
 599
 250600                return true;
 601            }
 0602            catch (FormatException)
 603            {
 0604                return false;
 605            }
 250606        }
 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>
 524613        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>
 2620        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) =>
 218628            (settings) =>
 218629            {
 650630                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 218631
 1936632                foreach (AccountSetting requirement in requiredSettings)
 218633                {
 754634                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 218635                    {
 536636                        result.Remove(requirement.Key);
 218637                    }
 218638                    else
 218639                    {
 436640                        return null;
 218641                    }
 218642                }
 218643
 432644                return result;
 218645            };
 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) =>
 272653            (settings) =>
 272654            {
 582655                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 272656
 2632657                foreach (AccountSetting requirement in optionalSettings)
 272658                {
 1142659                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 272660                    {
 672661                        result.Remove(requirement.Key);
 272662                    }
 272663                }
 272664
 582665                return result;
 272666            };
 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) =>
 54675            (settings) =>
 54676            {
 108677                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 108678                var foundOne = false;
 54679
 594680                foreach (AccountSetting requirement in atLeastOneSettings)
 54681                {
 270682                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 54683                    {
 186684                        result.Remove(requirement.Key);
 186685                        foundOne = true;
 54686                    }
 54687                }
 54688
 108689                return foundOne ? result : null;
 54690            };
 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) =>
 3699            (settings) =>
 3700            {
 219701                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 219702                var foundOne = false;
 3703
 1531704                foreach (AccountSetting requirement in atLeastOneSettings)
 3705                {
 551706                    if (result.TryGetValue(requirement.Key, out var value) && requirement.Value(value))
 3707                    {
 215708                        foundOne = true;
 3709                    }
 3710                }
 3711
 219712                return foundOne ? null : result;
 3713            };
 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) =>
 164721            (settings) =>
 164722            {
 542723                IDictionary<string, string> result = new Dictionary<string, string>(settings);
 164724
 2848725                foreach (ConnectionStringFilter filter in filters)
 164726                {
 1188727                    if (result == null)
 164728                    {
 164729                        break;
 164730                    }
 164731
 1068732                    result = filter(result);
 164733                }
 164734
 542735                return result;
 164736            };
 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) =>
 55744            (settings) =>
 55745            {
 217746                IDictionary<string, string>[] results =
 217747                    filters
 649748                    .Select(filter => filter(new Dictionary<string, string>(settings)))
 649749                    .Where(result => result != null)
 217750                    .Take(2)
 217751                    .ToArray();
 55752
 217753                return results.Length != 1 ? null : results.First();
 55754            };
 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) =>
 108762            (settings) =>
 108763            {
 216764                IDictionary<string, string> results = filter(settings);
 108765
 216766                return results == null || results.Any() ? null : results;
 108767            };
 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>
 1773        private static readonly ConnectionStringFilter s_validCredentials =
 1774            MatchesOne(
 1775                MatchesAll(AllRequired(s_accountNameSetting, s_accountKeySetting), Optional(s_accountKeyNameSetting), No
 1776                MatchesAll(AllRequired(s_sharedAccessSignatureSetting), Optional(s_accountNameSetting), None(s_accountKe
 1777                None(s_accountNameSetting, s_accountKeySetting, s_accountKeyNameSetting, s_sharedAccessSignatureSetting)
 1778            );
 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        {
 586794            foreach (ConnectionStringFilter constraint in constraints)
 795            {
 162796                IDictionary<string, string> remainingSettings = constraint(settings);
 797
 162798                if (remainingSettings == null)
 799                {
 62800                    return false;
 801                }
 802                else
 803                {
 100804                    settings = remainingSettings;
 805                }
 806            }
 807
 100808            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
 54819            settings.TryGetValue(Constants.ConnectionStrings.AccountNameSetting, out var accountName);
 54820            settings.TryGetValue(Constants.ConnectionStrings.AccountKeySetting, out var accountKey);
 54821            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
 54826            return
 54827                accountName != null && accountKey != null && sharedAccessSignature == null
 54828                ? new StorageSharedKeyCredential(accountName, accountKey/*, accountKeyName */)
 54829                : (object)(accountKey == null /* && accountKeyName == null */ && sharedAccessSignature != null
 54830                    ? new SharedAccessSignatureCredentials(sharedAccessSignature)
 54831                    : 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>
 10839        private static (Uri, Uri) ConstructBlobEndpoint(IDictionary<string, string> settings) => ConstructBlobEndpoint(
 10840                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 10841                settings[Constants.ConnectionStrings.AccountNameSetting],
 10842                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 10843                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        {
 12855            if (string.IsNullOrEmpty(scheme))
 856            {
 0857                throw Errors.ArgumentNull(nameof(scheme));
 858            }
 859
 12860            if (string.IsNullOrEmpty(accountName))
 861            {
 0862                throw Errors.ArgumentNull(nameof(accountName));
 863            }
 864
 12865            if (string.IsNullOrEmpty(endpointSuffix))
 866            {
 8867                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 868            }
 869
 12870            return ConstructUris(scheme, accountName, Constants.ConnectionStrings.DefaultBlobHostnamePrefix, endpointSuf
 871        }
 872
 873        /// <summary>
 874        /// Gets the default file endpoint using specified settings.
 875        /// </summary>
 876        /// <param name="settings">The settings to use.</param>
 877        /// <returns>The default file endpoint.</returns>
 6878        private static (Uri, Uri) ConstructFileEndpoint(IDictionary<string, string> settings) => ConstructFileEndpoint(
 6879                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 6880                settings[Constants.ConnectionStrings.AccountNameSetting],
 6881                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 6882                settings.ContainsKey(Constants.ConnectionStrings.SharedAccessSignatureSetting) ? settings[Constants.Conn
 883
 884        /// <summary>
 885        /// Gets the default file endpoint using the specified protocol and account name.
 886        /// </summary>
 887        /// <param name="scheme">The protocol to use.</param>
 888        /// <param name="accountName">The name of the storage account.</param>
 889        /// <param name="endpointSuffix">The Endpoint DNS suffix; use <c>null</c> for default.</param>
 890        /// <param name="sasToken">The sas token; use <c>null</c> for default.</param>
 891        /// <returns>The default file endpoint.</returns>
 892        internal static (Uri, Uri) ConstructFileEndpoint(string scheme, string accountName, string endpointSuffix, strin
 893        {
 10894            if (string.IsNullOrEmpty(scheme))
 895            {
 0896                throw Errors.ArgumentNull(nameof(scheme));
 897            }
 898
 10899            if (string.IsNullOrEmpty(accountName))
 900            {
 0901                throw Errors.ArgumentNull(nameof(accountName));
 902            }
 903
 10904            if (string.IsNullOrEmpty(endpointSuffix))
 905            {
 6906                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 907            }
 908
 10909            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>
 10917        private static (Uri, Uri) ConstructQueueEndpoint(IDictionary<string, string> settings) => ConstructQueueEndpoint
 10918                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 10919                settings[Constants.ConnectionStrings.AccountNameSetting],
 10920                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 10921                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            {
 8945                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>
 52956        private static (Uri, Uri) ConstructTableEndpoint(IDictionary<string, string> settings) => ConstructTableEndpoint
 52957                settings[Constants.ConnectionStrings.DefaultEndpointsProtocolSetting],
 52958                settings[Constants.ConnectionStrings.AccountNameSetting],
 52959                settings.ContainsKey(Constants.ConnectionStrings.EndpointSuffixSetting) ? settings[Constants.ConnectionS
 52960                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        {
 52972            if (string.IsNullOrEmpty(scheme))
 973            {
 0974                throw Errors.ArgumentNull(nameof(scheme));
 975            }
 976
 52977            if (string.IsNullOrEmpty(accountName))
 978            {
 0979                throw Errors.ArgumentNull(nameof(accountName));
 980            }
 981
 52982            if (string.IsNullOrEmpty(endpointSuffix))
 983            {
 50984                endpointSuffix = Constants.ConnectionStrings.DefaultEndpointSuffix;
 985            }
 986
 52987            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        {
 861006            var primaryUriBuilder = new UriBuilder
 861007            {
 861008                Scheme = scheme,
 861009                Host = string.Format(
 861010                        CultureInfo.InvariantCulture,
 861011                        "{0}.{1}.{2}",
 861012                        accountName,
 861013                        hostNamePrefix,
 861014                        endpointSuffix),
 861015                Query = sasToken
 861016            };
 1017
 861018            var secondaryUriBuilder = new UriBuilder
 861019            {
 861020                Scheme = scheme,
 861021                Host = string.Format(
 861022                        CultureInfo.InvariantCulture,
 861023                        "{0}{1}.{2}.{3}",
 861024                        accountName,
 861025                        Constants.ConnectionStrings.SecondaryLocationAccountSuffix,
 861026                        hostNamePrefix,
 861027                        endpointSuffix),
 861028                Query = sasToken
 861029            };
 1030
 861031            return (primaryUriBuilder.Uri, secondaryUriBuilder.Uri);
 1032        }
 1033    }
 1034}