Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.
/ corefx Public archive

Pass Uri.IdnHost to WinHttpConnect #28849

Merged
merged 13 commits into from
Apr 17, 2018
Merged

Pass Uri.IdnHost to WinHttpConnect #28849

merged 13 commits into from
Apr 17, 2018

Conversation

MarcoRossignoli
Copy link
Member

fixes #28722
replace PR #28818

cc: @davidsh @rmkerr @Caesar1995

@MarcoRossignoli MarcoRossignoli changed the title Pass Uri.IdnHost to WinHttpConnect [WIP]Pass Uri.IdnHost to WinHttpConnect Apr 5, 2018
@@ -783,7 +783,7 @@ private void EnsureSessionHandleExists(WinHttpRequestState state)
// Specify an HTTP server.
connectHandle = Interop.WinHttp.WinHttpConnect(
_sessionHandle,
state.RequestMessage.RequestUri.Host,
state.RequestMessage.RequestUri.HostNameType == UriHostNameType.IPv6 ? $"[{state.RequestMessage.RequestUri.IdnHost}]" : state.RequestMessage.RequestUri.IdnHost,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With regard to this i'm writing a manual test.

Copy link
Member Author

@MarcoRossignoli MarcoRossignoli Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidsh @rmkerr @Caesar1995 without [] ipv6 http://[::1234]:8080 and http://[::1234] tests fails with ERROR_INTERNET_INVALID_URL.

Copy link
Member Author

@MarcoRossignoli MarcoRossignoli Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Caesar1995 related to https://github.com/dotnet/corefx/issues/28863#issue-311719169 if this PR will be merged i think we'll need to remove workaround also here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, thanks for bringing this up! I will update that issue with this PR.

Assert.NotNull(ex.InnerException);

//ERROR_INTERNET_INVALID_URL https://msdn.microsoft.com/en-us/library/windows/desktop/aa385465(v=vs.85).aspx
Assert.NotEqual(12005, (int)ex.InnerException.GetType().GetProperty("NativeErrorCode").GetValue(ex.InnerException));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used reflection to avoid to add reference for WinHttpException

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to use reflection.

While WinHttpException is not a public type, it is derived from Win32Exception which is a public type. And the NativeErrorCode property can be accessed directly.
https://msdn.microsoft.com/en-us/library/system.componentmodel.win32exception(v=vs.110).aspx

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

[InlineData("http://[::1234]:8080", false, null)]
[InlineData("http://127.0.0.1", false, null)]
[InlineData("http://www.microsoft.com", false, "www.microsoft.com")]
public async Task ManualTest_IdnHostName(string requestUri, bool codeBase64, string requestHost)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidsh @rmkerr @Caesar1995 i tried to create a manual test, get back to me.

@MarcoRossignoli MarcoRossignoli changed the title [WIP]Pass Uri.IdnHost to WinHttpConnect Pass Uri.IdnHost to WinHttpConnect Apr 5, 2018
Take internationalized domain name 'Bangladesh' sample url from http://www.i18nguy.com/markup/idna-examples.html
Saved in codeBase64 to avoid Visual Studio complains about encoding
*/
[InlineData("aHR0cDovL+CmrOCmvuCmguCmsuCmvuCmpuCnh+Cmti5pY29tLm11c2V1bQ==", true, "icom.museum")]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't necessary to use Base64 encoding to create Unicode Uri literals. Just use "\uuuu" notation in the literals.

i.e. "http://\u09AC\u09BE\u0982\u09B2\u09BE\u09A6\u09C7\u09B6.icom.museum"

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@caesar-chen
Copy link
Contributor

@dotnet-bot test Outerloop Linux x64 Debug Build please
@dotnet-bot test Outerloop OSX x64 Debug Build please
@dotnet-bot test Outerloop Windows x64 Debug Build please

@MarcoRossignoli
Copy link
Member Author

@Caesar1995 seems unrelated issue, right?

[InlineData("http://[::1234]:8080", null)]
[InlineData("http://127.0.0.1", null)]
[InlineData("http://www.microsoft.com", "www.microsoft.com")]
public async Task ManualTest_IdnHostName(string requestUri, string requestHost)
Copy link
Contributor

@davidsh davidsh Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this manual test should be simplified. The only test we really need to add is an end-to-end test that validates that a request can be made to a server that uses Unicode characters in the DNS name. The other test cases are really already covered by other IPv4 and IPv6 literal test cases in System.Net.Http/tests/FunctionalTests.

Also, then, this test would be more valuable as a test case in the System.Net.Http/tests/FunctionalTests folder so that it can be run, manually, across the different handlers.

This test should also be marked in a way so that it doesn't get run all the time in CI because then it will be trying to connect all the time to a server that doesn't really exist.

The error handling then for HttpRequestException doesn't need to be present then since the test is only validating a successful operation when run.

Even better, would be if we could use a third-party server on the public Internet that uses Unicode characters in the DNS names. I'm sure that there are lots of them. Here is one that appears to be a international version of the Starbucks website.

Unicode: 스타벅스코리아.com
Punnycode: xn--oy2b35ckwhba574atvuzkc.com
\uuuu format: \uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com

So if you used that website, then the test could always be enabled and you would check for a successful connection.

Copy link
Member Author

@MarcoRossignoli MarcoRossignoli Apr 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidsh ok i'll move test to System.Net.Http/tests/FunctionalTests with simple successful operation check.

This test should also be marked in a way so that it doesn't get run all the time in CI because then it will be trying to connect all the time to a server that doesn't really exist

Do you mean [Outerloop] or do you have some test sample to take a cue?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you use the website (international Starbucks) that I described above, then you can simply mark the test as [Outerloop]. It will still get run all the time during outerloop CI runs. By default, tests in CI only run innerloop.

However, if the website was something that can't normally be reached, then you would mark this test in some other way so that it never runs in innerloop nor outerloop.

I recommend you simply try to use the website I indicated and then the test can run in outerloop all the time.

@MarcoRossignoli MarcoRossignoli changed the title Pass Uri.IdnHost to WinHttpConnect [WIP]Pass Uri.IdnHost to WinHttpConnect Apr 5, 2018
string idn = "\uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com";
using (HttpResponseMessage response = await client.GetAsync("http://" + idn))
{
response.EnsureSuccessStatusCode();
Copy link
Member Author

@MarcoRossignoli MarcoRossignoli Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidsh i added response.EnsureSuccessStatusCode(); as assertion.
Question on tests, i want to understand how do we test all handlers, I've noticed that test run two times, one for every handler, how it works?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In System.Net.Http.FunctionalTest project, we have abstract base test class, that's where you want to your test. The current location System.Net.Http/tests/FunctionalTests/HttpClientHandlerTest.cs is correct. Since the base class is abstract, those test cases will only be invoked by derived types (explained below).

There are two files which will actually invoke all the test cases. PlatformHandlerTest.cs, which will run the test against platform specific handlers (WinHttpHandler on Windows, and CurlHandler on Unix), and SocketsHttpHandler, which will run the test using our managed SocketsHttpHandler.

Even though you only modified the WinHttpHandler's code, we want to make sure the test you added is not specific to WinHttpHandler, and will pass with other handlers. To do so, you will help us detect/avoid platform specific behavior difference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Caesar1995 understood TYVM!

@@ -1659,7 +1659,7 @@ public void GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized()
}
});
}

Copy link
Member Author

@MarcoRossignoli MarcoRossignoli Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't know why...in my vs diff tool these changes weren't visible, should i remove?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, please remove any changes that aren't part of the PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@MarcoRossignoli MarcoRossignoli changed the title [WIP]Pass Uri.IdnHost to WinHttpConnect Pass Uri.IdnHost to WinHttpConnect Apr 6, 2018
@MarcoRossignoli
Copy link
Member Author

@dotnet-bot test Outerloop Linux x64 Debug Build please
@dotnet-bot test Outerloop OSX x64 Debug Build please
@dotnet-bot test Outerloop Windows x64 Debug Build please

string idn = "\uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com";
using (HttpResponseMessage response = await client.GetAsync("http://" + idn))
{
response.EnsureSuccessStatusCode();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidsh i added response.EnsureSuccessStatusCode(); as assertion.
Question on tests, i want to understand how do we test all handlers, I've noticed that test run two times, one for every handler, how it works?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explained in one of the comments above.

@@ -2138,6 +2138,25 @@ public void GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized()
});
}

[OuterLoop]
[Fact]
public async Task GetAsync_IdnHostName()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually the naming for the test function should be like: A_B_C, A is the attribute what we are testing about, B is the condition to trigger the attribute, and C is the behavior outcome.

You can change to something like GetAsync_IdnHostName_SuccessStatusCodeInResponse.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

/*
international version of the Starbucks website
Punnycode: xn--oy2b35ckwhba574atvuzkc.com
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In corefx, we usually do comment like this:

// Here is the comment.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@MarcoRossignoli
Copy link
Member Author

@dotnet-bot test Outerloop Linux x64 Debug Build please
@dotnet-bot test Outerloop OSX x64 Debug Build please
@dotnet-bot test Outerloop Windows x64 Debug Build please

HttpClientHandler handler = CreateHttpClientHandler();
using (var client = new HttpClient(handler))
{
//international version of the Starbucks website
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add space character after "//" comment characters.

{
//international version of the Starbucks website
//punnycode: xn--oy2b35ckwhba574atvuzkc.com
string idn = "\uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would name this variable to something like "server". "idn" implies that this string consists of punnycode character which it doesn't. It is simply a hostname of a endpoint that just happens to have Unicode characters in the name.

And I would simplify this by bringing in the "http://" portion of the string here as well, i.e.

string server = "http://\uc2a4\ud0c0\ubc85\uc2a4\ucf54\ub9ac\uc544.com";

@@ -2138,6 +2138,23 @@ public void GetAsync_ServerNeedsAuthAndNoCredential_StatusCodeUnauthorized()
});
}

[OuterLoop]
[Fact]
public async Task GetAsync_IdnHostName_SuccessStatusCodeInResponse()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this name to: "GetAsync_UnicodeHostName_SuccessStatusCodeInResponse"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, did you confirm that this test will fail without the fix in WinHttpHandler (to use IdnHost instead of Host)?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually no, i did only IdnHost square brackets tests. I'll do test.

Copy link
Member Author

@MarcoRossignoli MarcoRossignoli Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that WinHttp does not accept international host names without converting them first to Punycode

@davidsh...weird...seems to work, i tried also others url to test different characters...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@davidsh...weird...seems to work, i tried also others url to test different characters...

Perhaps WinHTTP has changed behavior on recent Windows OS versions to allow Unicode characters to be used in the API calls. However, if you look at WireShark traces, you'll probably see IDN (punnycode) encoding actually used underneath. Did you observe that?

It's possible that the documentation is out of date and needs to be corrected. This needs to be researched. @Caesar1995 can you start an offline email with WinHTTP team and ask them about this behavior?

For now, it would be useful to update your PR to remove the product code change in WinHttpHandler but keep the new test you added. Then run all the tests against this PR and see what passes/fails. Perhaps older Windows OS versions like Windows 7 might fail. We need to understand this more. It's possible that we don't need a product code change at all but the new test is useful.

Copy link
Member Author

@MarcoRossignoli MarcoRossignoli Apr 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i did a test with fiddler and i saw punycode going out, i'll restore WinHttpHandler...i apologize i assumed that guide was up to date, must never do.

@MarcoRossignoli
Copy link
Member Author

@dotnet-bot test Outerloop Linux x64 Debug Build please
@dotnet-bot test Outerloop OSX x64 Debug Build please
@dotnet-bot test Outerloop Windows x64 Debug Build please

@MarcoRossignoli
Copy link
Member Author

@dotnet-bot test Outerloop Windows x64 Debug Build please

@MarcoRossignoli
Copy link
Member Author

MarcoRossignoli commented Apr 9, 2018

@davidsh @Caesar1995 test fail on Windows.7.Amd64.Open-x64-Debug as you thought.
https://mc.dot.net/#/user/MarcoRossignoli/pr~2Fjenkins~2Fdotnet~2Fcorefx~2Fmaster~2F/test~2Ffunctional~2Fcli~2F/850743d62b18422f296b8ba028cfc9b640b56009/workItem/System.Net.Http.Functional.Tests/analysis/xunit/System.Net.Http.Functional.Tests.PlatformHandler_HttpClientHandlerTest~2FGetAsync_UnicodeHostName_SuccessStatusCodeInResponse

System.Net.Http.HttpRequestException : An error occurred while sending the request.
---- System.Net.Http.WinHttpException : Error 12005 calling WinHttpConnect, 'The URL is invalid'.

Maybe the guide is "conservative" for backward compatibility.
Do you agree if i re-add code change and re-run outerloop?

@MarcoRossignoli
Copy link
Member Author

@dotnet-bot test Outerloop Linux x64 Debug Build please
@dotnet-bot test Outerloop OSX x64 Debug Build please
@dotnet-bot test Outerloop Windows x64 Debug Build please

@MarcoRossignoli
Copy link
Member Author

MarcoRossignoli commented Apr 17, 2018

using (var client = new HttpClient(handler))
{
// international version of the Starbucks website
// punnycode: xn--oy2b35ckwhba574atvuzkc.com
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: punnycode => punycode

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank's

Copy link
Contributor

@davidsh davidsh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

@davidsh
Copy link
Contributor

davidsh commented Apr 17, 2018

@dotnet-bot Test Outerloop Windows x64 Debug Build

@davidsh
Copy link
Contributor

davidsh commented Apr 17, 2018

CI failures in OSX x64 Debug Build are unrelated to PR.

Will merge this once Outerloop Windows tests completes

Copy link
Contributor

@caesar-chen caesar-chen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for fixing this!

@caesar-chen caesar-chen merged commit b2893f0 into dotnet:master Apr 17, 2018
@MarcoRossignoli MarcoRossignoli deleted the winhttp2 branch April 17, 2018 17:44
picenka21 pushed a commit to picenka21/runtime that referenced this pull request Feb 18, 2022
* pass IdnHost

* add manual functional test

* cleanup

* fix comment

* address PR feedback

* address PR feedback

* address PR feedback

* address PR feedback

* address PR feedback

* address PR feedback

* address PR feedback

* fix IPv6 IdnHost

* address PR feedback


Commit migrated from dotnet/corefx@b2893f0
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

WinHttp should accept Uri.IdnHost for WinHttpConnect()
5 participants