| | 1 | | // Copyright (c) Microsoft Corporation. All rights reserved. |
| | 2 | | // Licensed under the MIT License. |
| | 3 | |
|
| | 4 | | using System; |
| | 5 | | using System.Collections.Generic; |
| | 6 | | using System.Globalization; |
| | 7 | | using System.Net; |
| | 8 | | using System.Text; |
| | 9 | |
|
| | 10 | | namespace Azure.Storage |
| | 11 | | { |
| | 12 | | /// <summary> |
| | 13 | | /// Extension methods used to manipulate URIs. |
| | 14 | | /// </summary> |
| | 15 | | internal static class UriExtensions |
| | 16 | | { |
| | 17 | | /// <summary> |
| | 18 | | /// Append a segment to a URIs path. |
| | 19 | | /// </summary> |
| | 20 | | /// <param name="uri">The URI.</param> |
| | 21 | | /// <param name="segment">The relative segment to append.</param> |
| | 22 | | /// <returns>The combined URI.</returns> |
| | 23 | | public static Uri AppendToPath(this Uri uri, string segment) |
| | 24 | | { |
| 557 | 25 | | var builder = new UriBuilder(uri); |
| 557 | 26 | | var path = builder.Path; |
| 557 | 27 | | var seperator = (path.Length == 0 || path[path.Length - 1] != '/') ? "/" : ""; |
| | 28 | | // In URLs, the percent sign is used to encode special characters, so if the segment |
| | 29 | | // has a percent sign in their URL path, we have to encode it before adding it to the path |
| 557 | 30 | | segment = segment.Replace(Constants.PercentSign, Constants.EncodedPercentSign); |
| 557 | 31 | | builder.Path += seperator + segment; |
| 557 | 32 | | return builder.Uri; |
| | 33 | | } |
| | 34 | |
|
| | 35 | | /// <summary> |
| | 36 | | /// Get the (already encoded) query parameters on a URI. |
| | 37 | | /// </summary> |
| | 38 | | /// <param name="uri">The URI.</param> |
| | 39 | | /// <returns>Dictionary mapping query parameters to values.</returns> |
| | 40 | | public static IDictionary<string, string> GetQueryParameters(this Uri uri) |
| | 41 | | { |
| 3292 | 42 | | var parameters = new Dictionary<string, string>(); |
| 3292 | 43 | | var query = uri.Query ?? ""; |
| 3292 | 44 | | if (!string.IsNullOrEmpty(query)) |
| | 45 | | { |
| 2882 | 46 | | if (query.StartsWith("?", true, CultureInfo.InvariantCulture)) |
| | 47 | | { |
| 2882 | 48 | | query = query.Substring(1); |
| | 49 | | } |
| 11874 | 50 | | foreach (var param in query.Split(new[] { '&' }, StringSplitOptions.RemoveEmptyEntries)) |
| | 51 | | { |
| 3055 | 52 | | var parts = param.Split(new[] { '=' }, 2); |
| 3055 | 53 | | var name = WebUtility.UrlDecode(parts[0]); |
| 3055 | 54 | | if (parts.Length == 1) |
| | 55 | | { |
| 0 | 56 | | parameters.Add(name, default); |
| | 57 | | } |
| | 58 | | else |
| | 59 | | { |
| 3055 | 60 | | parameters.Add(name, WebUtility.UrlDecode(parts[1])); |
| | 61 | | } |
| | 62 | | } |
| | 63 | | } |
| 3292 | 64 | | return parameters; |
| | 65 | | } |
| | 66 | |
|
| | 67 | | /// <summary> |
| | 68 | | /// Get the account name from the domain portion of a Uri. |
| | 69 | | /// </summary> |
| | 70 | | /// <param name="uri">The Uri.</param> |
| | 71 | | /// <param name="serviceSubDomain">The service subdomain used to validate that the |
| | 72 | | /// domain is in the expected format. This should be "blob" for blobs, "file" for files, |
| | 73 | | /// "queue" for queues, "blob" and "dfs" for datalake.</param> |
| | 74 | | /// <returns>Account name or null if not able to be parsed.</returns> |
| | 75 | | public static string GetAccountNameFromDomain(this Uri uri, string serviceSubDomain) => |
| 1161 | 76 | | GetAccountNameFromDomain(uri.Host, serviceSubDomain); |
| | 77 | |
|
| | 78 | | /// <summary> |
| | 79 | | /// Get the account name from the host. |
| | 80 | | /// </summary> |
| | 81 | | /// <param name="host">Host.</param> |
| | 82 | | /// <param name="serviceSubDomain">The service subdomain used to validate that the |
| | 83 | | /// domain is in the expected format. This should be "blob" for blobs, "file" for files, |
| | 84 | | /// "queue" for queues, "blob" and "dfs" for datalake.</param> |
| | 85 | | /// <returns>Account name or null if not able to be parsed.</returns> |
| | 86 | | public static string GetAccountNameFromDomain(string host, string serviceSubDomain) |
| | 87 | | { |
| 1161 | 88 | | var accountEndIndex = host.IndexOf(".", StringComparison.InvariantCulture); |
| 1161 | 89 | | if (accountEndIndex >= 0) |
| | 90 | | { |
| 1161 | 91 | | var serviceStartIndex = accountEndIndex + 1; |
| 1161 | 92 | | var serviceEndIndex = host.IndexOf(".", serviceStartIndex, StringComparison.InvariantCulture); |
| 1161 | 93 | | if (serviceEndIndex > serviceStartIndex) |
| | 94 | | { |
| 1159 | 95 | | var service = host.Substring(serviceStartIndex, serviceEndIndex - serviceStartIndex); |
| 1159 | 96 | | if (service == serviceSubDomain) |
| | 97 | | { |
| 1151 | 98 | | return host.Substring(0, accountEndIndex); |
| | 99 | | } |
| | 100 | | } |
| | 101 | | } |
| 10 | 102 | | return null; |
| | 103 | | } |
| | 104 | |
|
| | 105 | | /// <summary> |
| | 106 | | /// If path starts with a slash, remove it |
| | 107 | | /// </summary> |
| | 108 | | /// <param name="uri">The Uri.</param> |
| | 109 | | /// <returns>Sanitized Uri.</returns> |
| | 110 | | public static string GetPath(this Uri uri) => |
| 1167 | 111 | | (uri.AbsolutePath[0] == '/') ? |
| 1167 | 112 | | uri.AbsolutePath.Substring(1) : |
| 1167 | 113 | | uri.AbsolutePath; |
| | 114 | |
|
| | 115 | | // See remarks at https://docs.microsoft.com/en-us/dotnet/api/system.net.ipaddress.tryparse?view=netframework-4. |
| | 116 | | /// <summary> |
| | 117 | | /// Check to see if Uri is using IP Endpoint style. |
| | 118 | | /// </summary> |
| | 119 | | /// <param name="uri">The Uri.</param> |
| | 120 | | /// <returns>True if using IP Endpoint style.</returns> |
| | 121 | | public static bool IsHostIPEndPointStyle(this Uri uri) => |
| 1167 | 122 | | !string.IsNullOrEmpty(uri.Host) && |
| 1167 | 123 | | uri.Host.IndexOf(".", StringComparison.InvariantCulture) >= 0 && |
| 1167 | 124 | | IPAddress.TryParse(uri.Host, out _); |
| | 125 | |
|
| | 126 | | /// <summary> |
| | 127 | | /// Appends a query parameter to the string builder. |
| | 128 | | /// </summary> |
| | 129 | | /// <param name="sb">string builder instance.</param> |
| | 130 | | /// <param name="key">query parameter key.</param> |
| | 131 | | /// <param name="value">query parameter value.</param> |
| | 132 | | internal static void AppendQueryParameter(this StringBuilder sb, string key, string value) => |
| 0 | 133 | | sb |
| 0 | 134 | | .Append(sb.Length > 0 ? "&" : "") |
| 0 | 135 | | .Append(key) |
| 0 | 136 | | .Append('=') |
| 0 | 137 | | .Append(value); |
| | 138 | | } |
| | 139 | | } |