示例#1
0
        void ParseCookies(AndroidHttpResponseMessage ret, Uri connectionUri)
        {
            IEnumerable <string> cookieHeaderValue;

            if (!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null)
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"No cookies");
                }
                return;
            }

            try {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Parsing cookies");
                }
                CookieContainer.SetCookies(connectionUri, String.Join(",", cookieHeaderValue));
            } catch (Exception ex) {
                // We don't want to terminate the response because of a bad cookie, hence just reporting
                // the issue. We might consider adding a virtual method to let the user handle the
                // issue, but not sure if it's really needed. Set-Cookie header will be part of the
                // header collection so the user can always examine it if they spot an error.
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Failed to parse cookies in the server response. {ex.GetType ()}: {ex.Message}");
                }
            }
        }
示例#2
0
        async Task <HttpResponseMessage> DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState)
        {
            if (Logger.LogNet)
            {
                Logger.Log(LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()");
            }

            if (cancellationToken.IsCancellationRequested)
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, " cancelled");
                }

                cancellationToken.ThrowIfCancellationRequested();
            }

            try {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"  connecting");
                }

                await ConnectAsync(httpConnection, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);

                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"  connected");
                }
            } catch (Java.Net.ConnectException ex) {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Connection exception {ex}");
                }
                // Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler
                throw new WebException(ex.Message, ex, WebExceptionStatus.ConnectFailure, null);
            }

            if (cancellationToken.IsCancellationRequested)
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, " cancelled");
                }

                await DisconnectAsync(httpConnection).ConfigureAwait(continueOnCapturedContext: false);

                cancellationToken.ThrowIfCancellationRequested();
            }

            CancellationTokenRegistration cancelRegistration = default(CancellationTokenRegistration);
            HttpStatusCode statusCode    = HttpStatusCode.OK;
            Uri            connectionUri = null;

            try {
                cancelRegistration = cancellationToken.Register(() => {
                    DisconnectAsync(httpConnection).ContinueWith(t => {
                        if (t.Exception != null)
                        {
                            Logger.Log(LogLevel.Info, LOG_APP, $"Disconnection exception: {t.Exception}");
                        }
                    }, TaskScheduler.Default);
                }, useSynchronizationContext: false);

                if (httpConnection.DoOutput)
                {
                    using (var stream = await request.Content.ReadAsStreamAsync().ConfigureAwait(false)) {
                        await stream.CopyToAsync(httpConnection.OutputStream, 4096, cancellationToken)
                        .ConfigureAwait(false);
                    }
                }

                statusCode = await Task.Run(() => (HttpStatusCode)httpConnection.ResponseCode).ConfigureAwait(false);

                connectionUri = new Uri(httpConnection.URL.ToString());
            } finally {
                cancelRegistration.Dispose();
            }

            if (cancellationToken.IsCancellationRequested)
            {
                await DisconnectAsync(httpConnection).ConfigureAwait(continueOnCapturedContext: false);

                cancellationToken.ThrowIfCancellationRequested();
            }

            // If the request was redirected we need to put the new URL in the request
            request.RequestUri = connectionUri;
            var ret = new AndroidHttpResponseMessage(javaUrl, httpConnection)
            {
                RequestMessage = request,
                ReasonPhrase   = httpConnection.ResponseMessage,
                StatusCode     = statusCode,
            };

            if (Logger.LogNet)
            {
                Logger.Log(LogLevel.Info, LOG_APP, $"Status code: {statusCode}");
            }

            bool disposeRet;

            if (HandleRedirect(statusCode, httpConnection, redirectState, out disposeRet))
            {
                if (disposeRet)
                {
                    ret.Dispose();
                    ret = null;
                }
                return(ret);
            }

            switch (statusCode)
            {
            case HttpStatusCode.Unauthorized:
            case HttpStatusCode.ProxyAuthenticationRequired:
                // We don't resend the request since that would require new set of credentials if the
                // ones provided in Credentials are invalid (or null) and that, in turn, may require asking the
                // user which is not something that should be taken care of by us and in this
                // context. The application should be responsible for this.
                // HttpClientHandler throws an exception in this instance, but I think it's not a good
                // idea. We'll return the response message with all the information required by the
                // application to fill in the blanks and provide the requested credentials instead.
                //
                // We return the body of the response too, but the Java client will throw
                // a FileNotFound exception if we attempt to access the input stream.
                // Instead we try to read the error stream and return an default message if the error stream isn't readable.
                ret.Content = GetErrorContent(httpConnection, new StringContent("Unauthorized", Encoding.ASCII));
                CopyHeaders(httpConnection, ret);

                if (ret.Headers.WwwAuthenticate != null)
                {
                    ProxyAuthenticationRequested = false;
                    CollectAuthInfo(ret.Headers.WwwAuthenticate);
                }
                else if (ret.Headers.ProxyAuthenticate != null)
                {
                    ProxyAuthenticationRequested = true;
                    CollectAuthInfo(ret.Headers.ProxyAuthenticate);
                }

                ret.RequestedAuthentication = RequestedAuthentication;
                return(ret);
            }

            if (!IsErrorStatusCode(statusCode))
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Reading...");
                }
                ret.Content = GetContent(httpConnection, httpConnection.InputStream);
            }
            else
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Status code is {statusCode}, reading...");
                }
                // For 400 >= response code <= 599 the Java client throws the FileNotFound exception when attempting to read from the input stream.
                // Instead we try to read the error stream and return an empty string if the error stream isn't readable.
                ret.Content = GetErrorContent(httpConnection, new StringContent(String.Empty, Encoding.ASCII));
            }

            CopyHeaders(httpConnection, ret);

            IEnumerable <string> cookieHeaderValue;

            if (!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null)
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"No cookies");
                }
                return(ret);
            }

            try {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Parsing cookies");
                }
                CookieContainer.SetCookies(connectionUri, String.Join(",", cookieHeaderValue));
            } catch (Exception ex) {
                // We don't want to terminate the response because of a bad cookie, hence just reporting
                // the issue. We might consider adding a virtual method to let the user handle the
                // issue, but not sure if it's really needed. Set-Cookie header will be part of the
                // header collection so the user can always examine it if they spot an error.
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Failed to parse cookies in the server response. {ex.GetType ()}: {ex.Message}");
                }
            }

            if (Logger.LogNet)
            {
                Logger.Log(LogLevel.Info, LOG_APP, $"Returning");
            }
            return(ret);
        }
        HttpResponseMessage DoProcessRequest(HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken)
        {
            if (Logger.LogNet)
            {
                Logger.Log(LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()");
            }
            httpConnection.RequestMethod = request.Method.ToString();
            try {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"  connecting");
                }
                httpConnection.Connect();
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"  connected");
                }
            } catch (Java.Net.ConnectException ex) {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Connection exception {ex}");
                }
                // Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler
                throw new WebException(ex.Message, ex, WebExceptionStatus.ConnectFailure, null);
            }

            var statusCode    = (HttpStatusCode)httpConnection.ResponseCode;
            var connectionUri = new Uri(httpConnection.URL.ToString());

            // If the request was redirected we need to put the new URL in the request
            request.RequestUri = connectionUri;
            var ret = new AndroidHttpResponseMessage {
                RequestMessage = request,
                ReasonPhrase   = httpConnection.ResponseMessage,
                StatusCode     = statusCode,
            };

            if (Logger.LogNet)
            {
                Logger.Log(LogLevel.Info, LOG_APP, $"Status code: {statusCode}");
            }
            if (statusCode == HttpStatusCode.Unauthorized || statusCode == HttpStatusCode.ProxyAuthenticationRequired)
            {
                // We don't resend the request since that would require new set of credentials if the
                // ones provided in Credentials are invalid (or null) and that, in turn, may require asking the
                // user which is not something that should be taken care of by us and in this
                // context. The application should be responsible for this.
                // HttpClientHandler throws an exception in this instance, but I think it's not a good
                // idea. We'll return the response message with all the information required by the
                // application to fill in the blanks and provide the requested credentials instead.
                //
                // We should return the body of the response too but, alas, the Java client will throw
                // a, wait for it, FileNotFound exception if we attempt to access the input stream. So
                // no body, just a dummy. Java FTW!
                ret.Content = new StringContent("Unauthorized", Encoding.ASCII);
                CopyHeaders(httpConnection, ret);

                if (ret.Headers.WwwAuthenticate != null)
                {
                    ProxyAuthenticationRequested = false;
                    CollectAuthInfo(ret.Headers.WwwAuthenticate);
                }
                else if (ret.Headers.ProxyAuthenticate != null)
                {
                    ProxyAuthenticationRequested = true;
                    CollectAuthInfo(ret.Headers.ProxyAuthenticate);
                }

                ret.RequestedAuthentication = RequestedAuthentication;
                return(ret);
            }

            if (!IsErrorStatusCode(statusCode))
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Reading...");
                }
                Stream inputStream = new BufferedStream(httpConnection.InputStream);
                if (decompress_here)
                {
                    string[] encodings = httpConnection.ContentEncoding?.Split(',');
                    if (encodings != null)
                    {
                        if (encodings.Contains(GZIP_ENCODING, StringComparer.OrdinalIgnoreCase))
                        {
                            inputStream = new GZipStream(inputStream, CompressionMode.Decompress);
                        }
                        else if (encodings.Contains(DEFLATE_ENCODING, StringComparer.OrdinalIgnoreCase))
                        {
                            inputStream = new DeflateStream(inputStream, CompressionMode.Decompress);
                        }
                    }
                }
                ret.Content = new StreamContent(inputStream);
            }
            else
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Status code is {statusCode}, returning empty content");
                }
                // For 400 >= response code <= 599 the Java client throws the FileNotFound exeption when attempting to read from the connection
                // Client tests require we return no content here
                ret.Content = new StringContent(String.Empty, Encoding.ASCII);
            }
            CopyHeaders(httpConnection, ret);

            IEnumerable <string> cookieHeaderValue;

            if (!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null)
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"No cookies");
                }
                return(ret);
            }

            try {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Parsing cookies");
                }
                CookieContainer.SetCookies(connectionUri, String.Join(",", cookieHeaderValue));
            } catch (Exception ex) {
                // We don't want to terminate the response because of a bad cookie, hence just reporting
                // the issue. We might consider adding a virtual method to let the user handle the
                // issue, but not sure if it's really needed. Set-Cookie header will be part of the
                // header collection so the user can always examine it if they spot an error.
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Failed to parse cookies in the server response. {ex.GetType ()}: {ex.Message}");
                }
            }

            if (Logger.LogNet)
            {
                Logger.Log(LogLevel.Info, LOG_APP, $"Returning");
            }
            return(ret);
        }
示例#4
0
        async Task <HttpResponseMessage> DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState)
        {
            if (Logger.LogNet)
            {
                Logger.Log(LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()");
            }

            if (cancellationToken.IsCancellationRequested)
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, " cancelled");
                }

                cancellationToken.ThrowIfCancellationRequested();
            }

            try {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"  connecting");
                }

                await ConnectAsync(httpConnection, cancellationToken).ConfigureAwait(continueOnCapturedContext: false);

                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"  connected");
                }
            } catch (Java.Net.ConnectException ex) {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Connection exception {ex}");
                }
                // Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler
                throw new WebException(ex.Message, ex, WebExceptionStatus.ConnectFailure, null);
            }

            if (cancellationToken.IsCancellationRequested)
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, " cancelled");
                }

                await DisconnectAsync(httpConnection).ConfigureAwait(continueOnCapturedContext: false);

                cancellationToken.ThrowIfCancellationRequested();
            }

            CancellationTokenRegistration cancelRegistration = default(CancellationTokenRegistration);
            HttpStatusCode statusCode    = HttpStatusCode.OK;
            Uri            connectionUri = null;

            try {
                cancelRegistration = cancellationToken.Register(() => {
                    DisconnectAsync(httpConnection).ContinueWith(t => {
                        if (t.Exception != null)
                        {
                            Logger.Log(LogLevel.Info, LOG_APP, $"Disconnection exception: {t.Exception}");
                        }
                    }, TaskScheduler.Default);
                }, useSynchronizationContext: false);

                if (httpConnection.DoOutput)
                {
                    await WriteRequestContentToOutput(request, httpConnection, cancellationToken);
                }

                statusCode = await Task.Run(() => (HttpStatusCode)httpConnection.ResponseCode).ConfigureAwait(false);

                connectionUri = new Uri(httpConnection.URL.ToString());
            } finally {
                cancelRegistration.Dispose();
            }

            if (cancellationToken.IsCancellationRequested)
            {
                await DisconnectAsync(httpConnection).ConfigureAwait(continueOnCapturedContext: false);

                cancellationToken.ThrowIfCancellationRequested();
            }

            // If the request was redirected we need to put the new URL in the request
            request.RequestUri = connectionUri;
            var ret = new AndroidHttpResponseMessage(javaUrl, httpConnection)
            {
                RequestMessage = request,
                ReasonPhrase   = httpConnection.ResponseMessage,
                StatusCode     = statusCode,
            };

            if (Logger.LogNet)
            {
                Logger.Log(LogLevel.Info, LOG_APP, $"Status code: {statusCode}");
            }

            if (!IsErrorStatusCode(statusCode))
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Reading...");
                }
                ret.Content = GetContent(httpConnection, httpConnection.InputStream);
            }
            else
            {
                if (Logger.LogNet)
                {
                    Logger.Log(LogLevel.Info, LOG_APP, $"Status code is {statusCode}, reading...");
                }
                // For 400 >= response code <= 599 the Java client throws the FileNotFound exception when attempting to read from the input stream.
                // Instead we try to read the error stream and return an empty string if the error stream isn't readable.
                ret.Content = GetErrorContent(httpConnection, new StringContent(String.Empty, Encoding.ASCII));
            }

            bool disposeRet;

            if (HandleRedirect(statusCode, httpConnection, redirectState, out disposeRet))
            {
                if (redirectState.MethodChanged)
                {
                    // If a redirect uses GET but the original request used POST with content, then the redirected
                    // request will fail with an exception.
                    // There's also no way to send content using GET (except in the URL, of course), so discarding
                    // request.Content is what we should do.
                    //
                    // See https://github.com/xamarin/xamarin-android/issues/1282
                    if (redirectState.Method == HttpMethod.Get)
                    {
                        if (Logger.LogNet)
                        {
                            Logger.Log(LogLevel.Info, LOG_APP, $"Discarding content on redirect");
                        }
                        request.Content = null;
                    }
                }

                if (disposeRet)
                {
                    ret.Dispose();
                    ret = null;
                }
                else
                {
                    CopyHeaders(httpConnection, ret);
                    ParseCookies(ret, connectionUri);
                }

                return(ret);
            }

            switch (statusCode)
            {
            case HttpStatusCode.Unauthorized:
            case HttpStatusCode.ProxyAuthenticationRequired:
                // We don't resend the request since that would require new set of credentials if the
                // ones provided in Credentials are invalid (or null) and that, in turn, may require asking the
                // user which is not something that should be taken care of by us and in this
                // context. The application should be responsible for this.
                // HttpClientHandler throws an exception in this instance, but I think it's not a good
                // idea. We'll return the response message with all the information required by the
                // application to fill in the blanks and provide the requested credentials instead.
                //
                // We return the body of the response too, but the Java client will throw
                // a FileNotFound exception if we attempt to access the input stream.
                // Instead we try to read the error stream and return an default message if the error stream isn't readable.
                ret.Content = GetErrorContent(httpConnection, new StringContent("Unauthorized", Encoding.ASCII));
                CopyHeaders(httpConnection, ret);

                if (ret.Headers.WwwAuthenticate != null)
                {
                    ProxyAuthenticationRequested = false;
                    CollectAuthInfo(ret.Headers.WwwAuthenticate);
                }
                else if (ret.Headers.ProxyAuthenticate != null)
                {
                    ProxyAuthenticationRequested = true;
                    CollectAuthInfo(ret.Headers.ProxyAuthenticate);
                }

                ret.RequestedAuthentication = RequestedAuthentication;
                return(ret);
            }

            CopyHeaders(httpConnection, ret);
            ParseCookies(ret, connectionUri);

            if (Logger.LogNet)
            {
                Logger.Log(LogLevel.Info, LOG_APP, $"Returning");
            }
            return(ret);
        }
		HttpResponseMessage DoProcessRequest (HttpRequestMessage request, HttpURLConnection httpConnection, CancellationToken cancellationToken)
		{
			if (Logger.LogNet)
				Logger.Log (LogLevel.Info, LOG_APP, $"{this}.DoProcessRequest ()");
			httpConnection.RequestMethod = request.Method.ToString ();
			try {
				if (Logger.LogNet)
					Logger.Log (LogLevel.Info, LOG_APP, $"  connecting");
				httpConnection.Connect ();
				if (Logger.LogNet)
					Logger.Log (LogLevel.Info, LOG_APP, $"  connected");
			} catch (Java.Net.ConnectException ex) {
				if (Logger.LogNet)
					Logger.Log (LogLevel.Info, LOG_APP, $"Connection exception {ex}");
				// Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler
				throw new WebException (ex.Message, ex, WebExceptionStatus.ConnectFailure, null);
			}

			var statusCode = (HttpStatusCode)httpConnection.ResponseCode;
			var connectionUri = new Uri (httpConnection.URL.ToString ());

			// If the request was redirected we need to put the new URL in the request
			request.RequestUri = connectionUri;
			var ret = new AndroidHttpResponseMessage {
				RequestMessage = request,
				ReasonPhrase = httpConnection.ResponseMessage,
				StatusCode = statusCode,
			};

			if (Logger.LogNet)
				Logger.Log (LogLevel.Info, LOG_APP, $"Status code: {statusCode}");
			if (statusCode == HttpStatusCode.Unauthorized || statusCode == HttpStatusCode.ProxyAuthenticationRequired) {
				// We don't resend the request since that would require new set of credentials if the
				// ones provided in Credentials are invalid (or null) and that, in turn, may require asking the
				// user which is not something that should be taken care of by us and in this
				// context. The application should be responsible for this.
				// HttpClientHandler throws an exception in this instance, but I think it's not a good
				// idea. We'll return the response message with all the information required by the
				// application to fill in the blanks and provide the requested credentials instead.
				//
				// We should return the body of the response too but, alas, the Java client will throw
				// a, wait for it, FileNotFound exception if we attempt to access the input stream. So
				// no body, just a dummy. Java FTW!
				ret.Content = new StringContent ("Unauthorized", Encoding.ASCII);
				CopyHeaders (httpConnection, ret);

				if (ret.Headers.WwwAuthenticate != null) {
					ProxyAuthenticationRequested = false;
					CollectAuthInfo (ret.Headers.WwwAuthenticate);
				} else if (ret.Headers.ProxyAuthenticate != null) {
					ProxyAuthenticationRequested = true;
					CollectAuthInfo (ret.Headers.ProxyAuthenticate);
				}

				ret.RequestedAuthentication = RequestedAuthentication;
				return ret;
			}

			if (!IsErrorStatusCode (statusCode)) {
				if (Logger.LogNet)
					Logger.Log (LogLevel.Info, LOG_APP, $"Reading...");
				Stream inputStream = new BufferedStream (httpConnection.InputStream);
				if (decompress_here) {
					string[] encodings = httpConnection.ContentEncoding?.Split (',');
					if (encodings != null) {
						if (encodings.Contains (GZIP_ENCODING, StringComparer.OrdinalIgnoreCase))
							inputStream = new GZipStream (inputStream, CompressionMode.Decompress);
						else if (encodings.Contains (DEFLATE_ENCODING, StringComparer.OrdinalIgnoreCase))
							inputStream = new DeflateStream (inputStream, CompressionMode.Decompress);
					}
				}
				ret.Content = new StreamContent (inputStream);
			} else {
				if (Logger.LogNet)
					Logger.Log (LogLevel.Info, LOG_APP, $"Status code is {statusCode}, returning empty content");
				// For 400 >= response code <= 599 the Java client throws the FileNotFound exeption when attempting to read from the connection
				// Client tests require we return no content here
				ret.Content = new StringContent (String.Empty, Encoding.ASCII);
			}
			CopyHeaders (httpConnection, ret);

			IEnumerable <string> cookieHeaderValue;
			if (!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues ("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null) {
				if (Logger.LogNet)
					Logger.Log (LogLevel.Info, LOG_APP, $"No cookies");
				return ret;
			}

			try {
				if (Logger.LogNet)
					Logger.Log (LogLevel.Info, LOG_APP, $"Parsing cookies");
				CookieContainer.SetCookies (connectionUri, String.Join (",", cookieHeaderValue));
			} catch (Exception ex) {
				// We don't want to terminate the response because of a bad cookie, hence just reporting
				// the issue. We might consider adding a virtual method to let the user handle the
				// issue, but not sure if it's really needed. Set-Cookie header will be part of the
				// header collection so the user can always examine it if they spot an error.
				if (Logger.LogNet)
					Logger.Log (LogLevel.Info, LOG_APP, $"Failed to parse cookies in the server response. {ex.GetType ()}: {ex.Message}");
			}

			if (Logger.LogNet)
				Logger.Log (LogLevel.Info, LOG_APP, $"Returning");
			return ret;
		}