< Summary - SonghayCore

Line coverage
19%
Covered lines: 27
Uncovered lines: 110
Coverable lines: 137
Total lines: 405
Line coverage: 19.7%
Branch coverage
16%
Covered branches: 4
Total branches: 24
Branch coverage: 16.6%
Method coverage

Method coverage is only available for sponsors.

Upgrade to PRO version

Metrics

File(s)

/home/rasx/sourceRoot/SonghayCore/SonghayCore/Extensions/HttpRequestMessageExtensions._.cs

#LineLine coverage
 1namespace Songhay.Extensions;
 2
 3/// <summary>
 4/// Extensions of <see cref="HttpRequestMessage"/>
 5/// </summary>
 6public static partial class HttpRequestMessageExtensions
 7{
 8    /// <summary>
 9    /// Gets a <see cref="string"/> from the derived <see cref="HttpResponseMessage"/>.
 10    /// </summary>
 11    /// <param name="request">The request.</param>
 12    public static async Task<string> GetContentAsync(this HttpRequestMessage? request) =>
 013        await request.GetContentAsync(responseMessageAction: null, optionalClientGetter: null);
 14
 15    /// <summary>
 16    /// Gets a <see cref="string" /> from the derived <see cref="HttpResponseMessage" />.
 17    /// </summary>
 18    /// <param name="request">The request.</param>
 19    /// <param name="responseMessageAction">The response message action.</param>
 20    public static async Task<string> GetContentAsync(this HttpRequestMessage? request,
 21        Action<HttpResponseMessage>? responseMessageAction) =>
 122        await request.GetContentAsync(responseMessageAction, optionalClientGetter: null);
 23
 24    /// <summary>
 25    /// Gets a <see cref="string" /> from the derived <see cref="HttpResponseMessage" />.
 26    /// </summary>
 27    /// <param name="request">The request.</param>
 28    /// <param name="responseMessageAction">The response message action.</param>
 29    /// <param name="optionalClientGetter">The optional client getter.</param>
 30    public static async Task<string> GetContentAsync(this HttpRequestMessage? request,
 31        Action<HttpResponseMessage>? responseMessageAction,
 32        Func<HttpClient>? optionalClientGetter)
 133    {
 134        ArgumentNullException.ThrowIfNull(request);
 35
 136        var client = (optionalClientGetter == null) ? GetHttpClient() : optionalClientGetter.Invoke();
 37
 138        using var response = await client
 139            .SendAsync(request)
 140            .ConfigureAwait(continueOnCapturedContext: false);
 41
 142        responseMessageAction?.Invoke(response);
 43
 144        var content = await response.Content
 145            .ReadAsStringAsync()
 146            .ConfigureAwait(continueOnCapturedContext: false);
 47
 148        return content;
 149    }
 50
 51    /// <summary>
 52    /// Calls <see cref="HttpClient.SendAsync(HttpRequestMessage)" />
 53    /// </summary>
 54    /// <param name="request"></param>
 55    public static async Task<HttpResponseMessage> SendAsync(this HttpRequestMessage? request) =>
 356        await request.SendAsync(requestMessageAction: null,
 357            optionalClientGetter: null,
 358            HttpCompletionOption.ResponseContentRead);
 59
 60    /// <summary>
 61    /// Calls <see cref="HttpClient.SendAsync(HttpRequestMessage)" />
 62    /// </summary>
 63    /// <param name="request">The request.</param>
 64    /// <param name="requestMessageAction">The request message action.</param>
 65    public static async Task<HttpResponseMessage> SendAsync(this HttpRequestMessage? request,
 66        Action<HttpRequestMessage>? requestMessageAction) =>
 067        await request.SendAsync(requestMessageAction,
 068            optionalClientGetter: null,
 069            HttpCompletionOption.ResponseContentRead);
 70
 71    /// <summary>
 72    /// Calls <see cref="HttpClient.SendAsync(HttpRequestMessage)" />
 73    /// </summary>
 74    /// <param name="request">The request.</param>
 75    /// <param name="requestMessageAction">The request message action.</param>
 76    /// <param name="optionalClientGetter">The optional client getter.</param>
 77    /// <param name="completionOption"> the <see cref="HttpCompletionOption"/>.</param>
 78    public static async Task<HttpResponseMessage> SendAsync(this HttpRequestMessage? request,
 79        Action<HttpRequestMessage>? requestMessageAction,
 80        Func<HttpClient>? optionalClientGetter,
 81        HttpCompletionOption completionOption)
 482    {
 483        ArgumentNullException.ThrowIfNull(request);
 84
 485        requestMessageAction?.Invoke(request);
 86
 487        var client = (optionalClientGetter == null) ? GetHttpClient() : optionalClientGetter.Invoke();
 88
 489        var response = await client
 490            .SendAsync(request, completionOption)
 491            .ConfigureAwait(continueOnCapturedContext: false);
 92
 493        return response;
 494    }
 95
 96    /// <summary>
 97    /// Calls <see cref="HttpClient.SendAsync(HttpRequestMessage)" />
 98    /// with the specified request body, <see cref="Encoding.UTF8"/>
 99    /// and <see cref="MimeTypes.ApplicationJson"/>.
 100    /// </summary>
 101    /// <param name="request">The request.</param>
 102    /// <param name="requestBody">The request body.</param>
 103    public static async Task<HttpResponseMessage> SendBodyAsync(this HttpRequestMessage? request,
 0104        string? requestBody) => await request.SendBodyAsync(requestBody, Encoding.UTF8, MimeTypes.ApplicationJson);
 105
 106    /// <summary>
 107    /// Calls <see cref="HttpClient.SendAsync(HttpRequestMessage)" />
 108    /// with the specified request body.
 109    /// </summary>
 110    /// <param name="request">The request.</param>
 111    /// <param name="requestBody">The request body.</param>
 112    /// <param name="encoding">The encoding.</param>
 113    /// <param name="mediaType">Type of the media.</param>
 114    public static async Task<HttpResponseMessage> SendBodyAsync(this HttpRequestMessage? request,
 0115        string? requestBody, Encoding encoding, string mediaType) => await request.SendBodyAsync(requestBody,
 0116        encoding, mediaType, requestMessageAction: null, optionalClientGetter: null);
 117
 118    /// <summary>
 119    /// Calls <see cref="HttpClient.SendAsync(HttpRequestMessage)" />
 120    /// with the specified request body.
 121    /// </summary>
 122    /// <param name="request">The request.</param>
 123    /// <param name="requestBody">The request body.</param>
 124    /// <param name="encoding">The encoding.</param>
 125    /// <param name="mediaType">Type of the media.</param>
 126    /// <param name="requestMessageAction">The request message action.</param>
 127    public static async Task<HttpResponseMessage> SendBodyAsync(this HttpRequestMessage? request,
 128        string? requestBody, Encoding encoding, string mediaType,
 0129        Action<HttpRequestMessage> requestMessageAction) => await request.SendBodyAsync(requestBody, encoding,
 0130        mediaType, requestMessageAction, optionalClientGetter: null);
 131
 132    /// <summary>
 133    /// Calls <see cref="HttpClient.SendAsync(HttpRequestMessage)" />
 134    /// with the specified request body.
 135    /// </summary>
 136    /// <param name="request">The request.</param>
 137    /// <param name="requestBody">The request body.</param>
 138    /// <param name="encoding">The encoding.</param>
 139    /// <param name="mediaType">Type of the media.</param>
 140    /// <param name="requestMessageAction">The request message action.</param>
 141    /// <param name="optionalClientGetter">The optional client getter.</param>
 142    public static async Task<HttpResponseMessage> SendBodyAsync(this HttpRequestMessage? request,
 143        string? requestBody, Encoding? encoding, string? mediaType,
 144        Action<HttpRequestMessage>? requestMessageAction,
 145        Func<HttpClient>? optionalClientGetter)
 0146    {
 0147        ArgumentNullException.ThrowIfNull(request);
 0148        requestBody.ThrowWhenNullOrWhiteSpace();
 0149        mediaType.ThrowWhenNullOrWhiteSpace();
 150
 0151        request.Content = new StringContent(requestBody, encoding, mediaType);
 152
 0153        requestMessageAction?.Invoke(request);
 154
 0155        var client = (optionalClientGetter == null) ? GetHttpClient() : optionalClientGetter.Invoke();
 156
 0157        var response = await client
 0158            .SendAsync(request)
 0159            .ConfigureAwait(continueOnCapturedContext: false);
 160
 0161        return response;
 0162    }
 163
 5164    static HttpClient GetHttpClient() => HttpClientLazy.Value;
 165
 2166    static readonly Lazy<HttpClient> HttpClientLazy = new(() => new HttpClient(), LazyThreadSafetyMode.PublicationOnly);
 167}

/home/rasx/sourceRoot/SonghayCore/SonghayCore/Extensions/HttpRequestMessageExtensions.AzureStorage.cs

#LineLine coverage
 1using System.Net.Http.Headers;
 2using System.Security.Cryptography;
 3
 4namespace Songhay.Extensions;
 5
 6public static partial class HttpRequestMessageExtensions
 7{
 8    /// <summary>
 9    /// Derives the <see cref="AuthenticationHeaderValue"/>
 10    /// from the <see cref="HttpRequestMessage"/>.
 11    /// </summary>
 12    /// <param name="request">the <see cref="HttpRequestMessage"/></param>
 13    /// <param name="storageAccountName">the Azure Storage account name</param>
 14    /// <param name="storageAccountKey">the Azure Storage account shared key</param>
 15    /// <param name="eTag">entity tag for Web cache validation</param>
 16    /// <param name="md5">The MD5 (message-digest algorithm) hash</param>
 17    /// <remarks>
 18    /// There are two Authorization Header schemes supported: SharedKey and SharedKeyLite. This member supports only one
 19    /// For more detail, see “Specifying the Authorization header”
 20    /// [ https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key#specifying-the-authorizati
 21    ///
 22    /// See also: “Authorize requests to Azure Storage”
 23    /// [ https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-requests-to-azure-storage ]
 24    ///
 25    /// See also: https://github.com/Azure-Samples/storage-dotnet-rest-api-with-auth/tree/master
 26    ///
 27    /// Provide the md5 and it will check and make sure it matches the requested blob's md5.
 28    /// If it doesn't match, it won't return a value.
 29    ///
 30    /// Provide an eTag, and it will only make changes to a blob if the current eTag matches,
 31    /// to ensure you don't overwrite someone else's changes.
 32    /// </remarks>
 33    public static AuthenticationHeaderValue ToAzureStorageAuthorizationHeader(this HttpRequestMessage? request,
 34        string? storageAccountName, string? storageAccountKey, string? eTag, string? md5)
 035    {
 036        ArgumentNullException.ThrowIfNull(request);
 037        storageAccountKey.ThrowWhenNullOrWhiteSpace();
 38
 039        var signatureBytes = request.ToAzureStorageSignature(storageAccountName, eTag, md5);
 40
 041        var sha256 = new HMACSHA256(Convert.FromBase64String(storageAccountKey));
 42
 43        const string scheme = "SharedKey";
 044        var parameter = $"{storageAccountName}:{Convert.ToBase64String(sha256.ComputeHash(signatureBytes))}";
 45
 046        var value = new AuthenticationHeaderValue(scheme, parameter);
 47
 048        return value;
 049    }
 50
 51    /// <summary>
 52    /// Returns headers, starting with <c>x-ms-</c>,
 53    /// in a canonical format.
 54    /// </summary>
 55    /// <param name="request">the <see cref="HttpRequestMessage"/></param>
 56    /// <remarks>
 57    /// See https://github.com/Azure-Samples/storage-dotnet-rest-api-with-auth/tree/master
 58    ///
 59    /// See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-requests-to-azure-storage
 60    ///
 61    /// See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
 62    ///
 63    /// See http://en.wikipedia.org/wiki/Canonicalization
 64    /// </remarks>
 65    public static string ToAzureStorageCanonicalizedHeaders(this HttpRequestMessage? request)
 066    {
 067        ArgumentNullException.ThrowIfNull(request);
 68
 069        var xMsHeaders = request.Headers
 070            .Where(pair => pair.Key.StartsWith("x-ms-", StringComparison.OrdinalIgnoreCase))
 071            .OrderBy(pair => pair.Key)
 072            .Select(pair => new {Key = pair.Key.ToLowerInvariant(), pair.Value});
 73
 074        var sb = new StringBuilder();
 75
 076        foreach (var pair in xMsHeaders)
 077        {
 078            var innerBuilder = new StringBuilder(pair.Key);
 079            char separator = ':';
 80
 081            foreach (string headerValues in pair.Value)
 082            {
 083                string trimmedValue = headerValues.TrimStart().Replace("\r\n", String.Empty);
 084                innerBuilder.Append(separator).Append(trimmedValue);
 85
 86                // Set this to a comma; this will only be used
 87                //   if there are multiple values for one of the headers.
 088                separator = ',';
 089            }
 90
 091            sb.Append(innerBuilder.ToString()).Append("\n");
 092        }
 93
 094        return sb.ToString();
 095    }
 96
 97    /// <summary>
 98    /// Derives the raw representation of the message signature
 99    /// from the <see cref="HttpRequestMessage"/>.`
 100    /// </summary>
 101    /// <param name="request">the <see cref="HttpRequestMessage"/></param>
 102    /// <param name="storageAccountName">The name of the storage account to use.</param>
 103    /// <param name="eTag">entity tag for Web cache validation</param>
 104    /// <param name="md5">The MD5 (message-digest algorithm) hash</param>
 105    /// <remarks>
 106    /// See https://github.com/Azure-Samples/storage-dotnet-rest-api-with-auth/tree/master
 107    ///
 108    /// See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-requests-to-azure-storage
 109    ///
 110    /// See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
 111    /// </remarks>
 112    public static byte[] ToAzureStorageSignature(this HttpRequestMessage? request, string? storageAccountName,
 113        string? eTag, string? md5)
 0114    {
 0115        ArgumentNullException.ThrowIfNull(request);
 0116        if (request.Content == null)
 0117            throw new NullReferenceException($"{nameof(request)}.{nameof(request.Content)}");
 0118        if (string.IsNullOrWhiteSpace(eTag)) eTag = string.Empty;
 0119        if (string.IsNullOrWhiteSpace(md5)) md5 = string.Empty;
 120
 0121        var contentLength = string.Empty;
 122
 0123        HttpMethod method = request.Method;
 124
 0125        if (method == HttpMethod.Put)
 0126        {
 127            try
 0128            {
 0129                contentLength = request.Content.Headers.ContentLength.ToString();
 0130            }
 0131            catch (NullReferenceException ex)
 0132            {
 0133                throw new NullReferenceException(
 0134                    $"The expected Content Headers are not here. Was {nameof(HttpRequestMessage)}.{nameof(HttpRequestMes
 0135                    ex);
 136            }
 0137        }
 138
 0139        string canonicalizedHeaders = request.ToAzureStorageCanonicalizedHeaders();
 0140        string location = request.RequestUri.ToAzureStorageCanonicalizedResourceLocation(storageAccountName);
 141
 0142        var messageSignature =
 0143            $"{method}\n\n\n{contentLength}\n{md5}\n\n\n\n{eTag}\n\n\n\n{canonicalizedHeaders}{location}";
 144
 0145        byte[] signatureBytes = Encoding.UTF8.GetBytes(messageSignature);
 146
 0147        return signatureBytes;
 0148    }
 149
 150    /// <summary>
 151    /// Returns <see cref="HttpRequestMessage"/>
 152    /// with conventional headers for <see cref="ByteArrayContent"/>
 153    /// for Azure Storage.
 154    /// </summary>
 155    /// <param name="request">the <see cref="HttpRequestMessage"/></param>
 156    /// <param name="blobName">the Azure Storage Blob name</param>
 157    /// <param name="content">the Azure Storage Blob content</param>
 158    public static HttpRequestMessage WithAzureStorageBlockBlobContent(this HttpRequestMessage? request,
 159        string? blobName, string? content)
 0160    {
 0161        ArgumentNullException.ThrowIfNull(request);
 0162        ArgumentNullException.ThrowIfNull(blobName);
 0163        ArgumentNullException.ThrowIfNull(content);
 164
 0165        byte[] bytes = Encoding.UTF8.GetBytes(content);
 166
 0167        request.Content = new ByteArrayContent(bytes);
 168
 0169        request.Headers.Add("x-ms-blob-content-disposition", $@"attachment; filename=""{blobName}""");
 0170        request.Headers.Add("x-ms-blob-type", "BlockBlob");
 0171        request.Headers.Add("x-ms-meta-m1", "v1");
 0172        request.Headers.Add("x-ms-meta-m2", "v2");
 173
 0174        return request;
 0175    }
 176
 177    /// <summary>
 178    /// Returns <see cref="HttpRequestMessage"/>
 179    /// with conventional headers for Azure Storage.
 180    /// </summary>
 181    /// <param name="request"></param>
 182    /// <param name="requestMoment"></param>
 183    /// <param name="serviceVersion"></param>
 184    /// <param name="storageAccountName"></param>
 185    /// <param name="storageAccountKey"></param>
 186    public static HttpRequestMessage WithAzureStorageHeaders(this HttpRequestMessage? request,
 187        DateTime requestMoment, string? serviceVersion, string? storageAccountName, string? storageAccountKey)
 0188    {
 0189        return request.WithAzureStorageHeaders(
 0190            requestMoment,
 0191            serviceVersion,
 0192            storageAccountName,
 0193            storageAccountKey,
 0194            eTag: null,
 0195            md5: null
 0196        );
 0197    }
 198
 199    /// <summary>
 200    /// Returns <see cref="HttpRequestMessage"/> with the minimum headers
 201    /// required for Azure Storage.
 202    /// </summary>
 203    /// <param name="request">the <see cref="HttpRequestMessage"/></param>
 204    /// <param name="requestMoment">the moment of the request</param>
 205    /// <param name="serviceVersion"></param>
 206    /// <param name="storageAccountName">the Azure Storage account name</param>
 207    /// <param name="storageAccountKey">the Azure Storage account shared key</param>
 208    /// <param name="eTag">entity tag for Web cache validation</param>
 209    /// <param name="md5">The MD5 (message-digest algorithm) hash</param>
 210    /// <remarks>
 211    /// See https://github.com/Azure-Samples/storage-dotnet-rest-api-with-auth/tree/master
 212    ///
 213    /// See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-requests-to-azure-storage
 214    ///
 215    /// See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
 216    ///
 217    /// Provide the md5 and it will check and make sure it matches the requested blob's md5. If it doesn't match, it won
 218    ///
 219    /// Provide an eTag, and it will only make changes to a blob if the current eTag matches, to ensure you don't overwr
 220    /// </remarks>
 221    public static HttpRequestMessage WithAzureStorageHeaders(this HttpRequestMessage? request,
 222        DateTime requestMoment, string? serviceVersion, string? storageAccountName, string? storageAccountKey,
 223        string? eTag, string? md5)
 0224    {
 0225        ArgumentNullException.ThrowIfNull(request);
 226
 0227        request.Headers.Add("x-ms-date", requestMoment.ToString("R", CultureInfo.InvariantCulture));
 0228        request.Headers.Add("x-ms-version", serviceVersion);
 229
 0230        request.Headers.Authorization =
 0231            request.ToAzureStorageAuthorizationHeader(
 0232                storageAccountName,
 0233                storageAccountKey,
 0234                eTag, md5);
 235
 0236        return request;
 0237    }
 238}