Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cookies are not added to cookie container when running on iOS #2168

Open
h-arshad opened this issue Feb 2, 2024 · 6 comments
Open

Cookies are not added to cookie container when running on iOS #2168

h-arshad opened this issue Feb 2, 2024 · 6 comments
Labels

Comments

@h-arshad
Copy link

h-arshad commented Feb 2, 2024

When using rest sharp in NET MAUI application development, for the same API call that is supposed to inject a cookie, in Android you will see the cookie but it is missing when running in iOS device.

Code Sample:

async Task TestCookiesAsync(){
var restClient = new RestSharp.RestClient(new RestSharp.RestClientOptions()
{
  BaseUrl = new Uri("https://google.com"),
  CookieContainer = new System.Net.CookieContainer()
});
var request = new RestSharp.RestRequest("", RestSharp.Method.Get);
var response = await restClient.ExecuteAsync(request);
Console.WriteLine(response.Cookies.Count);
}

Expected behaviour:
The response cookie should be added inside the cookie container.

  • OS: iOS 17.2.1 (iPhone 11 )
  • NET MAUI .NET 8
  • Rest Sharp V 110.2.0
@h-arshad h-arshad added the bug label Feb 2, 2024
@rassilon
Copy link

Based on what I see at https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/HttpClientHandler.AnyMobile.InvokeNativeHandler.cs

This probably has something to do with the differences between the native handlers on iOS vs Android.
i.e. Xamarin.Android.Net.AndroidMessageHandler, Mono.Android vs System.Net.Http.NSUrlSessionHandler, Microsoft.iOS

If a redirect is involved, it might be related to #2059, although I wonder why the behavior would differ between mobile platforms. :(

@h-arshad
Copy link
Author

Thank you for your response and looking into the post.
In fact I do not suspect the issue comes from those handlers because eventually due to the cookie issue I had with Restsharp, I replaced it with the native HttpClient and it works fine on both platforms.
I put an example of the working code below.

private static readonly System.Net.Http.HttpClientHandler HttpClientMessageHandler = new()
 {
     AllowAutoRedirect = false,
     UseCookies = true,
     CookieContainer = new(),
 };
 private static readonly System.Net.Http.HttpClient RestClient = new(HttpClientMessageHandler)
 {
     BaseAddress = new("https://google.com"),
     Timeout = TimeSpan.FromSeconds(60),
     DefaultRequestHeaders =
     {
         { "User-Agent", "mobile"},
         { "Accept", "application/json" }
     }
 };

The one can simply use var response = await RestClient.GetAsync();

@alexeyzimarev
Copy link
Member

alexeyzimarev commented May 26, 2024

@h-arshad RestSharp handles cookies without using an external CookieContainer. It might have worked if you have just removed the cookie container from the client setup.

It could also be that .NET on iOS has some differences concerning getting cookies from the container based on the URL.

Basically, what RestSharp does: it creates a new cookie container for each request, unless the request itself has a cookie container. But, the container is not used for making a call. Instead, RestSharp adds cookie headers to the request.
When RestSharp receives a response with SetCookie headers, it adds those values to the cookie container.

The code looks like this:

public static void AddCookies(this CookieContainer cookieContainer, Uri uri, IEnumerable<string> cookiesHeader) {
    foreach (var header in cookiesHeader) {
        try {
            cookieContainer.SetCookies(uri, header);
        }
        catch (CookieException) {
            // Do not fail request if we cannot parse a cookie
        }
    }
}

You see there that sometimes adding a cookie to the container fails. I am not sure if that's what happens on iOS.

@newky2k
Copy link

newky2k commented Jun 4, 2024

@h-arshad I have the same issue with RestClient not returning the cookies on MAUI for iOS.

I was able to resolve it by passing an instance of HttpClient which had the CookieContainer set on its HttpClientHandler (similar to the working code you posted above).

I think setting it on the handler via RestClientOptions.ConfigureMessageHandler should probably work too, though i haven't tested it.

The below code would probably also work with IHttpClientFactory, by using that to create the HttpClient instance(and configuring the CookieContainer), but again not tested.

 var handler = new HttpClientHandler { CookieContainer = _cookieManager };
 var client = new HttpClient(handler);

 var options = new RestClientOptions()
 {
     BaseUrl = new Uri(Connection.Url),
     Timeout = TimeSpan.FromSeconds(60),
     CookieContainer = _cookieManager,
 };

 return new RestClient(client, options);

@alexeyzimarev its feels like the ConfigureHttpMessageHandler method should be applying Options.CookieContainer to the handler, even though you say that it doesn't.

Setting the CookieContainer on the request didn't fix the problem on iOS MAUI, neither did not setting Options.CookieContainer

    static void ConfigureHttpMessageHandler(HttpClientHandler handler, RestClientOptions options) {
#if NET
        if (!OperatingSystem.IsBrowser()) {
#endif
        handler.UseCookies             = false;
        handler.Credentials            = options.Credentials;
        handler.UseDefaultCredentials  = options.UseDefaultCredentials;
        handler.AutomaticDecompression = options.AutomaticDecompression;
        handler.PreAuthenticate        = options.PreAuthenticate;
        if (options.MaxRedirects.HasValue) handler.MaxAutomaticRedirections = options.MaxRedirects.Value;

        if (options.RemoteCertificateValidationCallback != null)
            handler.ServerCertificateCustomValidationCallback =
                (request, cert, chain, errors) => options.RemoteCertificateValidationCallback(request, cert, chain, errors);

        if (options.ClientCertificates != null) {
            handler.ClientCertificates.AddRange(options.ClientCertificates);
            handler.ClientCertificateOptions = ClientCertificateOption.Manual;
        }

@alexeyzimarev
Copy link
Member

It's weird that the issue only affects one particular platform. RestSharp doesn't do any kind of magic with cookies, and the reason why it doesn't set the cookie container property of the handler is because otherwise handling cookies will deviate from what's already there when the container isn't used.

Basically, after making a call, RestSharp tries to find cookie headers, collect the values, and add cookies to the response and to the Options.CookieContainer. It's really strange that it doesn't work on iOS.

            // Parse all the cookies from the response and update the cookie jar with cookies
            if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var cookiesHeader)) {
                // ReSharper disable once PossibleMultipleEnumeration
                cookieContainer.AddCookies(url, cookiesHeader);
                // ReSharper disable once PossibleMultipleEnumeration
                Options.CookieContainer?.AddCookies(url, cookiesHeader);
            }

@h-arshad
Copy link
Author

h-arshad commented Jul 30, 2024

@alexeyzimarev Thank you for the suggestion. I tried to make the request on sample code without initializing the cookie container, but it did not work. In fact, after looking into the response object and comparing the Android and iOS, I realized that on iOS the ResstSharp.RestResponse object has 9 headers and Android has 17 headers after the call completes. Strangely enough, on iOS there are no headers for Set-Cookie, it seems the cookie container is not the main problem after all.

@h-arshad h-arshad reopened this Jul 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

4 participants