TL; DR: nie używaj zaakceptowanej wersji, ponieważ jest całkowicie zepsuta w związku z obsługą znaków Unicode i nigdy nie używaj wewnętrznego API
Znalazłem dziwny problem podwójnego kodowania z zaakceptowanym rozwiązaniem:
Jeśli więc masz do czynienia ze znakami, które muszą zostać zakodowane, zaakceptowane rozwiązanie prowadzi do podwójnego kodowania:
- parametry zapytania są automatycznie kodowane przy użyciu
NameValueCollection
indeksatora ( i używa tego UrlEncodeUnicode
, nie jest to normalnie oczekiwane UrlEncode
(!) )
- Następnie po wywołaniu
uriBuilder.Uri
tworzy nowy Uri
za pomocą konstruktora, który koduje jeszcze raz (normalne kodowanie adresu URL)
- Tego nie da się uniknąć poprzez zrobienie
uriBuilder.ToString()
(nawet jeśli to zwraca poprawne, Uri
która IMO jest co najmniej niespójnością, być może błędem, ale to kolejne pytanie), a następnie za pomocą HttpClient
metody akceptującej ciąg - klient nadal tworzy Uri
z przekazanego ciągu w następujący sposób:new Uri(uri, UriKind.RelativeOrAbsolute)
Małe, ale pełne repro:
var builder = new UriBuilder
{
Scheme = Uri.UriSchemeHttps,
Port = -1,
Host = "127.0.0.1",
Path = "app"
};
NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);
query["cyrillic"] = "кирилиця";
builder.Query = query.ToString();
Console.WriteLine(builder.Query); //query with cyrillic stuff UrlEncodedUnicode, and that's not what you want
var uri = builder.Uri; // creates new Uri using constructor which does encode and messes cyrillic parameter even more
Console.WriteLine(uri);
// this is still wrong:
var stringUri = builder.ToString(); // returns more 'correct' (still `UrlEncodedUnicode`, but at least once, not twice)
new HttpClient().GetStringAsync(stringUri); // this creates Uri object out of 'stringUri' so we still end up sending double encoded cyrillic text to server. Ouch!
Wynik:
?cyrillic=%u043a%u0438%u0440%u0438%u043b%u0438%u0446%u044f
https://127.0.0.1/app?cyrillic=%25u043a%25u0438%25u0440%25u0438%25u043b%25u0438%25u0446%25u044f
Jak widać, bez względu na to, czy zrobisz uribuilder.ToString()
+ httpClient.GetStringAsync(string)
lub uriBuilder.Uri
+ httpClient.GetStringAsync(Uri)
, ostatecznie wyślesz podwójnie zakodowany parametr
Naprawionym przykładem może być:
var uri = new Uri(builder.ToString(), dontEscape: true);
new HttpClient().GetStringAsync(uri);
Ale używa to przestarzałego Uri
konstruktora
PS w moim najnowszym .NET na Windows Server, Uri
konstruktor z komentarzem bool doc mówi „przestarzały, dontEscape jest zawsze fałszywy”, ale w rzeczywistości działa zgodnie z oczekiwaniami (pomija ucieczkę)
Wygląda to na kolejny błąd ...
I nawet to jest po prostu źle - wysyła UrlEncodedUnicode do serwera, a nie tylko UrlEncoded, czego oczekuje serwer
Aktualizacja: jeszcze jedno: NameValueCollection faktycznie wykonuje UrlEncodeUnicode, którego nie należy już używać i jest niezgodny ze zwykłym url.encode / decode (patrz NameValueCollection do zapytania URL? ).
Więc sedno brzmi: nigdy nie używaj tego hacka,NameValueCollection query = HttpUtility.ParseQueryString(builder.Query);
ponieważ spowoduje to bałagan parametrów zapytania unicode. Wystarczy zbudować zapytanie ręcznie i przypisać je do tego, UriBuilder.Query
które wykona niezbędne kodowanie, a następnie użyje Uri UriBuilder.Uri
.
Doskonały przykład zranienia się za pomocą kodu, którego nie należy używać w ten sposób