2

I have the following code to set content-md5 in my GET method request using HttpClient

httpClient.DefaultRequestHeaders.TryAddWithoutValidation("content-md5", "value");

I cannot use HttpRequestMessage content to set it because it's not a POST method. When using Postman it works like a charm but fails when using HttpClient.GetAsync. Client request a hmac to the server as follows

{ "content_to_hash": "my content" }

The server will give response like this

{ "content_md5": "88af7ceab9fdafb76xxxxx", "date": "Sat, 02 May 2020 00:13:16 +0700", "hmac_value": "WfHgFyT792IENmK8Mqz9LysmP8ftOP00qA=" }

Now I have to access a GET request using that hmac where it's the problem because I cannot set in httpClient GET request header.

Here's the image

enter image description here

derodevil
  • 811
  • 1
  • 11
  • 37
  • Eh? Sorry, why can't you set the Content-MD5 on the Content? – ProgrammingLlama May 02 '20 at 05:42
  • GET method has no content – derodevil May 02 '20 at 05:43
  • So why do you want to set Content-MD5? ([Content-MD5](https://www.oreilly.com/library/view/http-the-definitive/1565925092/re17.html): _"The Content-MD5 header is used by servers to provide a message-integrity check for the message body. "_) – ProgrammingLlama May 02 '20 at 05:44
  • The api is not mine. I need to set it because the api requires it. You can see the image – derodevil May 02 '20 at 05:46
  • OK. What are you supposed to set for `Content-MD5` in the case that there is no content? Always `d41d8cd98f00b204e9800998ecf8427e` (MD5 of `string.Empty`)? – ProgrammingLlama May 02 '20 at 05:47
  • Why can postman do it? – derodevil May 02 '20 at 05:48
  • Because Postman doesn't validate the parameters you send? – ProgrammingLlama May 02 '20 at 05:48
  • All I need i to set it like postman do, without validation but it fails. – derodevil May 02 '20 at 05:50
  • Do you definitely need `Content-MD5`? Since `Content-MD5` without any content is meaningless (what would it be a hash of? nothing?). – ProgrammingLlama May 02 '20 at 05:52
  • Is Content-MD5 being used in an unconventional way such that it isn't a hash of the Content, but of the request or something? – ProgrammingLlama May 02 '20 at 05:58
  • I think the image I attached described everything. Without content-md5 header the request fails. – derodevil May 02 '20 at 06:01
  • And I do want to make this as clear as I possibly can for you: `Content-MD5` represents a hash of the request body. If there is no request body, there is nothing to hash. – ProgrammingLlama May 02 '20 at 06:08
  • Anyway, it seems that [Content-MD5](https://github.com/microsoft/referencesource/blob/aaca53b025f41ab638466b1efe569df314f689ea/System/net/System/Net/Http/Headers/HttpContentHeaders.cs#L162) is added to a list of "invalid headers" which is then set on the request-level [header collection](https://github.com/microsoft/referencesource/blob/aaca53b025f41ab638466b1efe569df314f689ea/System/net/System/Net/Http/Headers/HttpHeaders.cs#L367) – ProgrammingLlama May 02 '20 at 06:30
  • This causes causes [TryCheckHeaderName](https://github.com/microsoft/referencesource/blob/aaca53b025f41ab638466b1efe569df314f689ea/System/net/System/Net/Http/Headers/HttpHeaders.cs#L1145) to return `false` and in turn [`TryAddWithoutValidation` returns false](https://github.com/microsoft/referencesource/blob/aaca53b025f41ab638466b1efe569df314f689ea/System/net/System/Net/Http/Headers/HttpHeaders.cs#L105). Your only hope might be to replace `HttpRequestHeaders` with your own. – ProgrammingLlama May 02 '20 at 06:30

2 Answers2

0

From reading the HttpClient and related source code, there's no way you can get around this and add the header to the actual request object headers. There is an internal list of invalid headers, which includes any Content-* headers. It has to be on a content object.

Therefore, my suggest solution is to create your own content object:

public class NoContentMd5 : HttpContent
{
    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        return Task.CompletedTask;
    }

    protected override bool TryComputeLength(out long length)
    {
        length = 0;
        return false;
    }

    public NoContentMd5(byte[] contentMd5)
    {
        this.Headers.ContentMD5 = contentMd5;
    }

    public NoContentMd5(string contentMd5)
    {
        this.Headers.TryAddWithoutValidation("Content-MD5", contentMd5);
    }
}

This will add the Content-MD5 header with a value of your choosing, but the request won't contain a body.

The next problem you'll encounter is that you're trying to make a GET request with content, which isn't supported by the helper client.GetAsync(...) method. You'll have to make your own request object and use client.SendAsync(...) instead:

HttpClient client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost/my/test/uri");
request.Content = new NoContentMd5("d41d8cd98f00b204e9800998ecf8427e ");
var result = await client.SendAsync(request);

Note that if you have your Content-MD5 hash as bytes, I've also added a constructor to NoContentMd5 for byte[] too.

The only potential issue with this is that it includes a Content-Length: 0 header. Hopefully that's OK with the API you're working with.

There's an alternative solution described in this answer to question with a similar issue. I'd argue against using it since is vulnerable to changes in the implementation details of HttpRequestHeaders (because it uses reflection, so if MS change the code, it might break) .

ProgrammingLlama
  • 36,677
  • 7
  • 67
  • 86
-1

Aside from the fact that it's not considered good practice to send a body with GET request (see HTTP GET with request body), you can try this:

using (var content = new StringContent(string.Empty))
using (var request = new HttpRequestMessage
{
    Method = HttpMethod.Get,
    RequestUri = new Uri("http://localhost"),
    Content = content
})
{
    request.Headers.TryAddWithoutValidation("content-md5", "value");;
    using (var response = await httpClient.SendAsync(request))
    {
        response.EnsureSuccessStatusCode();
    }
}

UPDATE:

The proper way would be to set the ContentMD5 property on the HttpContentHeaders, for example:

content.Headers.ContentMD5 = Convert.FromBase64String(hashString);

But as you pointed out in the comments, trying to send content in a GET request causes an error.

crgolden
  • 4,332
  • 1
  • 22
  • 40
  • Error. Cannot send a content-body with this vert-type. – derodevil May 02 '20 at 06:57
  • Removing content makes the request successfully sent but the `content-md5` header is still not set – derodevil May 02 '20 at 07:03
  • As I explained in the comments, `TryAddWithoutValidation` returns `false` because `Content-MD5` isn't an allowed non-content header. I've also supplied necessary links to the sections of Microsoft's code so that you can verify this yourself. – ProgrammingLlama May 02 '20 at 07:09