From a6bc44dc10155f2d6a4b88c42568e8440f6acf2e Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Thu, 20 Apr 2017 14:22:11 -0400 Subject: [PATCH] No need for custom handler anymore - xam bug fixed --- src/Android/Android.csproj | 1 - src/Android/CustomAndroidClientHandler.cs | 774 ---------------------- src/Android/Services/HttpService.cs | 2 +- 3 files changed, 1 insertion(+), 776 deletions(-) delete mode 100644 src/Android/CustomAndroidClientHandler.cs diff --git a/src/Android/Android.csproj b/src/Android/Android.csproj index 49b285392..f9f9dbd06 100644 --- a/src/Android/Android.csproj +++ b/src/Android/Android.csproj @@ -316,7 +316,6 @@ - diff --git a/src/Android/CustomAndroidClientHandler.cs b/src/Android/CustomAndroidClientHandler.cs deleted file mode 100644 index 654d85bfa..000000000 --- a/src/Android/CustomAndroidClientHandler.cs +++ /dev/null @@ -1,774 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.IO.Compression; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -using Android.Runtime; -using Java.IO; -using Java.Net; -using Java.Security; -using Java.Security.Cert; -using Javax.Net.Ssl; - -namespace Xamarin.Android.Net -{ - /// - /// A custom implementation of which internally uses - /// (or its HTTPS incarnation) to send HTTP requests. - /// - /// - /// Instance of this class is used to configure instance - /// in the following way: - /// - /// - /// var handler = new AndroidClientHandler { - /// UseCookies = true, - /// AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip, - /// }; - /// - /// var httpClient = new HttpClient (handler); - /// var response = httpClient.GetAsync ("http://example.com")?.Result as AndroidHttpResponseMessage; - /// - /// - /// The class supports pre-authentication of requests albeit in a slightly "manual" way. Namely, whenever a request to a server requiring authentication - /// is made and no authentication credentials are provided in the property (which is usually the case on the first - /// request), the property will return true and the property will - /// contain all the authentication information gathered from the server. The application must then fill in the blanks (i.e. the credentials) and re-send - /// the request configured to perform pre-authentication. The reason for this manual process is that the underlying Java HTTP client API supports only a - /// single, VM-wide, authentication handler which cannot be configured to handle credentials for several requests. AndroidClientHandler, therefore, implements - /// the authentication in managed .NET code. Message handler supports both Basic and Digest authentication. If an authentication scheme that's not supported - /// by AndroidClientHandler is requested by the server, the application can provide its own authentication module (, - /// ) to handle the protocol authorization. - /// AndroidClientHandler also supports requests to servers with "invalid" (e.g. self-signed) SSL certificates. Since this process is a bit convoluted using - /// the Java APIs, AndroidClientHandler defines two ways to handle the situation. First, easier, is to store the necessary certificates (either CA or server certificates) - /// in the collection or, after deriving a custom class from AndroidClientHandler, by overriding one or more methods provided for this purpose - /// (, and ). The former method should be sufficient - /// for most use cases, the latter allows the application to provide fully customized key store, trust manager and key manager, if needed. Note that the instance of - /// AndroidClientHandler configured to accept an "invalid" certificate from the particular server will most likely fail to validate certificates from other servers (even - /// if they use a certificate with a fully validated trust chain) unless you store the CA certificates from your Android system in along with - /// the self-signed certificate(s). - /// - public class CustomAndroidClientHandler : HttpClientHandler - { - sealed class RequestRedirectionState - { - public Uri NewUrl; - public int RedirectCounter; - public HttpMethod Method; - } - - internal const string LOG_APP = "monodroid-net"; - - const string GZIP_ENCODING = "gzip"; - const string DEFLATE_ENCODING = "deflate"; - const string IDENTITY_ENCODING = "identity"; - - static readonly HashSet known_content_headers = new HashSet(StringComparer.OrdinalIgnoreCase) { - "Allow", - "Content-Disposition", - "Content-Encoding", - "Content-Language", - "Content-Length", - "Content-Location", - "Content-MD5", - "Content-Range", - "Content-Type", - "Expires", - "Last-Modified" - }; - - static readonly List authModules = new List { - new AuthModuleBasic (), - // COMMENTED OUT: Kyle - //new AuthModuleDigest () - }; - - bool disposed; - - // Now all hail Java developers! Get this... HttpURLClient defaults to accepting AND - // uncompressing the gzip content encoding UNLESS you set the Accept-Encoding header to ANY - // value. So if we set it to 'gzip' below we WILL get gzipped stream but HttpURLClient will NOT - // uncompress it any longer, doh. And they don't support 'deflate' so we need to handle it ourselves. - bool decompress_here; - - /// - /// - /// Gets or sets the pre authentication data for the request. This property must be set by the application - /// before the request is made. Generally the value can be taken from - /// after the initial request, without any authentication data, receives the authorization request from the - /// server. The application must then store credentials in instance of and - /// assign the instance to this propery before retrying the request. - /// - /// - /// The property is never set by AndroidClientHandler. - /// - /// - /// The pre authentication data. - public AuthenticationData PreAuthenticationData { get; set; } - - /// - /// If the website requires authentication, this property will contain data about each scheme supported - /// by the server after the response. Note that unauthorized request will return a valid response - you - /// need to check the status code and and (re)configure AndroidClientHandler instance accordingly by providing - /// both the credentials and the authentication scheme by setting the - /// property. If AndroidClientHandler is not able to detect the kind of authentication scheme it will store an - /// instance of with its property - /// set to AuthenticationScheme.Unsupported and the application will be responsible for providing an - /// instance of which handles this kind of authorization scheme - /// ( - /// - public IList RequestedAuthentication { get; private set; } - - /// - /// Server authentication response indicates that the request to authorize comes from a proxy if this property is true. - /// All the instances of stored in the property will - /// have their preset to the same value as this property. - /// - public bool ProxyAuthenticationRequested { get; private set; } - - /// - /// If true then the server requested authorization and the application must use information - /// found in to set the value of - /// - public bool RequestNeedsAuthorization - { - get { return RequestedAuthentication?.Count > 0; } - } - - /// - /// - /// If the request is to the server protected with a self-signed (or otherwise untrusted) SSL certificate, the request will - /// fail security chain verification unless the application provides either the CA certificate of the entity which issued the - /// server's certificate or, alternatively, provides the server public key. Whichever the case, the certificate(s) must be stored - /// in this property in order for AndroidClientHandler to configure the request to accept the server certificate. - /// AndroidClientHandler uses a custom and to configure the connection. - /// If, however, the application requires finer control over the SSL configuration (e.g. it implements its own TrustManager) then - /// it should leave this property empty and instead derive a custom class from AndroidClientHandler and override, as needed, the - /// , and methods - /// instead - /// - /// The trusted certs. - public IList TrustedCerts { get; set; } - - protected override void Dispose(bool disposing) - { - disposed = true; - - base.Dispose(disposing); - } - - protected void AssertSelf() - { - if(!disposed) - return; - throw new ObjectDisposedException(nameof(AndroidClientHandler)); - } - - string EncodeUrl(Uri url) - { - if(url == null) - return String.Empty; - - if(String.IsNullOrEmpty(url.Query)) - return Uri.EscapeUriString(url.ToString()); - - // UriBuilder takes care of encoding everything properly - var bldr = new UriBuilder(url); - if(url.IsDefaultPort) - bldr.Port = -1; // Avoids adding :80 or :443 to the host name in the result - - // bldr.Uri.ToString () would ruin the good job UriBuilder did - return bldr.ToString(); - } - - /// - /// Creates, configures and processes an asynchronous request to the indicated resource. - /// - /// Task in which the request is executed - /// Request provided by - /// Cancellation token. - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - AssertSelf(); - if(request == null) - throw new ArgumentNullException(nameof(request)); - - if(!request.RequestUri.IsAbsoluteUri) - throw new ArgumentException("Must represent an absolute URI", "request"); - - var redirectState = new RequestRedirectionState - { - NewUrl = request.RequestUri, - RedirectCounter = 0, - Method = request.Method - }; - while(true) - { - URL java_url = new URL(EncodeUrl(redirectState.NewUrl)); - URLConnection java_connection = java_url.OpenConnection(); - HttpURLConnection httpConnection = await SetupRequestInternal(request, java_connection); - HttpResponseMessage response = await ProcessRequest(request, java_url, httpConnection, cancellationToken, redirectState); - if(response != null) - return response; - - if(redirectState.NewUrl == null) - throw new InvalidOperationException("Request redirected but no new URI specified"); - request.Method = redirectState.Method; - } - } - - Task ProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) - { - cancellationToken.ThrowIfCancellationRequested(); - httpConnection.InstanceFollowRedirects = false; // We handle it ourselves - RequestedAuthentication = null; - ProxyAuthenticationRequested = false; - - return DoProcessRequest(request, javaUrl, httpConnection, cancellationToken, redirectState); - } - - async Task DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState) - { - if(cancellationToken.IsCancellationRequested) - { - cancellationToken.ThrowIfCancellationRequested(); - } - - try - { - await Task.WhenAny( - httpConnection.ConnectAsync(), - Task.Run(() => { cancellationToken.WaitHandle.WaitOne(); })) - .ConfigureAwait(false); - } - catch(Java.Net.ConnectException 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) - { - httpConnection.Disconnect(); - cancellationToken.ThrowIfCancellationRequested(); - } - cancellationToken.Register(httpConnection.Disconnect); - - if(httpConnection.DoOutput) - { - using(var stream = await request.Content.ReadAsStreamAsync()) - { - await stream.CopyToAsync(httpConnection.OutputStream, 4096, cancellationToken) - .ConfigureAwait(false); - } - } - - if(cancellationToken.IsCancellationRequested) - { - httpConnection.Disconnect(); - cancellationToken.ThrowIfCancellationRequested(); - } - - var statusCode = await Task.Run(() => (HttpStatusCode)httpConnection.ResponseCode).ConfigureAwait(false); - 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(javaUrl, httpConnection) - { - RequestMessage = request, - ReasonPhrase = httpConnection.ResponseMessage, - StatusCode = 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); - } - - // COMMENTED OUT: Kyle - //ret.RequestedAuthentication = RequestedAuthentication; - return ret; - } - - if(!IsErrorStatusCode(statusCode)) - { - ret.Content = GetContent(httpConnection, httpConnection.InputStream); - } - else - { - // 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 cookieHeaderValue; - if(!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null) - { - return ret; - } - - try - { - 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. - } - - return ret; - } - - HttpContent GetErrorContent(HttpURLConnection httpConnection, HttpContent fallbackContent) - { - var contentStream = httpConnection.ErrorStream; - - if(contentStream != null) - { - return GetContent(httpConnection, contentStream); - } - - return fallbackContent; - } - - HttpContent GetContent(URLConnection httpConnection, Stream contentStream) - { - Stream inputStream = new BufferedStream(contentStream); - 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); - } - } - return new StreamContent(inputStream); - } - - bool HandleRedirect(HttpStatusCode redirectCode, HttpURLConnection httpConnection, RequestRedirectionState redirectState, out bool disposeRet) - { - if(!AllowAutoRedirect) - { - disposeRet = false; - return true; // We shouldn't follow and there's no data to fetch, just return - } - disposeRet = true; - - redirectState.NewUrl = null; - switch(redirectCode) - { - case HttpStatusCode.MultipleChoices: // 300 - break; - - case HttpStatusCode.Moved: // 301 - case HttpStatusCode.Redirect: // 302 - case HttpStatusCode.SeeOther: // 303 - redirectState.Method = HttpMethod.Get; - break; - - case HttpStatusCode.TemporaryRedirect: // 307 - break; - - default: - if((int)redirectCode >= 300 && (int)redirectCode < 400) - throw new InvalidOperationException($"HTTP Redirection status code {redirectCode} ({(int)redirectCode}) not supported"); - return false; - } - - IDictionary> headers = httpConnection.HeaderFields; - IList locationHeader; - if(!headers.TryGetValue("Location", out locationHeader) || locationHeader == null || locationHeader.Count == 0) - throw new InvalidOperationException($"HTTP connection redirected with code {redirectCode} ({(int)redirectCode}) but no Location header found in response"); - - - redirectState.RedirectCounter++; - if(redirectState.RedirectCounter >= MaxAutomaticRedirections) - throw new WebException($"Maximum automatic redirections exceeded (allowed {MaxAutomaticRedirections}, redirected {redirectState.RedirectCounter} times)"); - - Uri location = new Uri(locationHeader[0], UriKind.Absolute); - redirectState.NewUrl = location; - - return true; - } - - bool IsErrorStatusCode(HttpStatusCode statusCode) - { - return (int)statusCode >= 400 && (int)statusCode <= 599; - } - - void CollectAuthInfo(HttpHeaderValueCollection headers) - { - var authData = new List(headers.Count); - - foreach(AuthenticationHeaderValue ahv in headers) - { - var data = new AuthenticationData - { - Scheme = GetAuthScheme(ahv.Scheme), - // COMMENTED OUT: Kyle - //Challenge = $"{ahv.Scheme} {ahv.Parameter}", - UseProxyAuthentication = ProxyAuthenticationRequested - }; - authData.Add(data); - } - - RequestedAuthentication = authData.AsReadOnly(); - } - - AuthenticationScheme GetAuthScheme(string scheme) - { - if(String.Compare("basic", scheme, StringComparison.OrdinalIgnoreCase) == 0) - return AuthenticationScheme.Basic; - if(String.Compare("digest", scheme, StringComparison.OrdinalIgnoreCase) == 0) - return AuthenticationScheme.Digest; - - return AuthenticationScheme.Unsupported; - } - - void CopyHeaders(HttpURLConnection httpConnection, HttpResponseMessage response) - { - IDictionary> headers = httpConnection.HeaderFields; - foreach(string key in headers.Keys) - { - if(key == null) // First header entry has null key, it corresponds to the response message - continue; - - HttpHeaders item_headers; - string kind; - if(known_content_headers.Contains(key)) - { - kind = "content"; - item_headers = response.Content.Headers; - } - else - { - kind = "response"; - item_headers = response.Headers; - } - item_headers.TryAddWithoutValidation(key, headers[key]); - } - } - - /// - /// Configure the before the request is sent. This method is meant to be overriden - /// by applications which need to perform some extra configuration steps on the connection. It is called with all - /// the request headers set, pre-authentication performed (if applicable) but before the request body is set - /// (e.g. for POST requests). The default implementation in AndroidClientHandler does nothing. - /// - /// Request data - /// Pre-configured connection instance - protected virtual Task SetupRequest(HttpRequestMessage request, HttpURLConnection conn) - { - return Task.Factory.StartNew(AssertSelf); - } - - /// - /// Configures the key store. The parameter is set to instance of - /// created using the type and with populated with certificates provided in the - /// property. AndroidClientHandler implementation simply returns the instance passed in the parameter - /// - /// The key store. - /// Key store to configure. - protected virtual KeyStore ConfigureKeyStore(KeyStore keyStore) - { - AssertSelf(); - - return keyStore; - } - - /// - /// Create and configure an instance of . The parameter is set to the - /// return value of the method, so it might be null if the application overrode the method and provided - /// no key store. It will not be null when the default implementation is used. The application can return null here since - /// KeyManagerFactory is not required for the custom SSL configuration, but it might be used by the application to implement a more advanced - /// mechanism of key management. - /// - /// The key manager factory or null. - /// Key store. - protected virtual KeyManagerFactory ConfigureKeyManagerFactory(KeyStore keyStore) - { - AssertSelf(); - - return null; - } - - /// - /// Create and configure an instance of . The parameter is set to the - /// return value of the method, so it might be null if the application overrode the method and provided - /// no key store. It will not be null when the default implementation is used. The application can return null from this - /// method in which case AndroidClientHandler will create its own instance of the trust manager factory provided that the - /// list contains at least one valid certificate. If there are no valid certificates and this method returns null, no custom - /// trust manager will be created since that would make all the HTTPS requests fail. - /// - /// The trust manager factory. - /// Key store. - protected virtual TrustManagerFactory ConfigureTrustManagerFactory(KeyStore keyStore) - { - AssertSelf(); - - return null; - } - - void AppendEncoding(string encoding, ref List list) - { - if(list == null) - list = new List(); - if(list.Contains(encoding)) - return; - list.Add(encoding); - } - - async Task SetupRequestInternal(HttpRequestMessage request, URLConnection conn) - { - if(conn == null) - throw new ArgumentNullException(nameof(conn)); - var httpConnection = conn.JavaCast(); - if(httpConnection == null) - throw new InvalidOperationException($"Unsupported URL scheme {conn.URL.Protocol}"); - - httpConnection.RequestMethod = request.Method.ToString(); - - // SSL context must be set up as soon as possible, before adding any content or - // headers. Otherwise Java won't use the socket factory - SetupSSL(httpConnection as HttpsURLConnection); - if(request.Content != null) - AddHeaders(httpConnection, request.Content.Headers); - AddHeaders(httpConnection, request.Headers); - - List accept_encoding = null; - - decompress_here = false; - if((AutomaticDecompression & DecompressionMethods.GZip) != 0) - { - AppendEncoding(GZIP_ENCODING, ref accept_encoding); - decompress_here = true; - } - - if((AutomaticDecompression & DecompressionMethods.Deflate) != 0) - { - AppendEncoding(DEFLATE_ENCODING, ref accept_encoding); - decompress_here = true; - } - - if(AutomaticDecompression == DecompressionMethods.None) - { - accept_encoding?.Clear(); - AppendEncoding(IDENTITY_ENCODING, ref accept_encoding); // Turns off compression for the Java client - } - - if(accept_encoding?.Count > 0) - httpConnection.SetRequestProperty("Accept-Encoding", String.Join(",", accept_encoding)); - - if(UseCookies && CookieContainer != null) - { - string cookieHeaderValue = CookieContainer.GetCookieHeader(request.RequestUri); - if(!String.IsNullOrEmpty(cookieHeaderValue)) - httpConnection.SetRequestProperty("Cookie", cookieHeaderValue); - } - - HandlePreAuthentication(httpConnection); - await SetupRequest(request, httpConnection); - SetupRequestBody(httpConnection, request); - - return httpConnection; - } - - void SetupSSL(HttpsURLConnection httpsConnection) - { - if(httpsConnection == null) - return; - - KeyStore keyStore = KeyStore.GetInstance(KeyStore.DefaultType); - keyStore.Load(null, null); - bool gotCerts = TrustedCerts?.Count > 0; - if(gotCerts) - { - for(int i = 0; i < TrustedCerts.Count; i++) - { - Certificate cert = TrustedCerts[i]; - if(cert == null) - continue; - keyStore.SetCertificateEntry($"ca{i}", cert); - } - } - keyStore = ConfigureKeyStore(keyStore); - KeyManagerFactory kmf = ConfigureKeyManagerFactory(keyStore); - TrustManagerFactory tmf = ConfigureTrustManagerFactory(keyStore); - - if(tmf == null) - { - // If there are no certs and no trust manager factory, we can't use a custom manager - // because it will cause all the HTTPS requests to fail because of unverified trust - // chain - if(!gotCerts) - return; - - tmf = TrustManagerFactory.GetInstance(TrustManagerFactory.DefaultAlgorithm); - tmf.Init(keyStore); - } - - SSLContext context = SSLContext.GetInstance("TLS"); - context.Init(kmf?.GetKeyManagers(), tmf.GetTrustManagers(), null); - httpsConnection.SSLSocketFactory = context.SocketFactory; - } - - void HandlePreAuthentication(HttpURLConnection httpConnection) - { - AuthenticationData data = PreAuthenticationData; - if(!PreAuthenticate || data == null) - return; - - ICredentials creds = data.UseProxyAuthentication ? Proxy?.Credentials : Credentials; - if(creds == null) - { - return; - } - - IAndroidAuthenticationModule auth = data.Scheme == AuthenticationScheme.Unsupported ? data.AuthModule : authModules.Find(m => m?.Scheme == data.Scheme); - if(auth == null) - { - return; - } - - Authorization authorization = auth.Authenticate(data.Challenge, httpConnection, creds); - if(authorization == null) - { - return; - } - - httpConnection.SetRequestProperty(data.UseProxyAuthentication ? "Proxy-Authorization" : "Authorization", authorization.Message); - } - - void AddHeaders(HttpURLConnection conn, HttpHeaders headers) - { - if(headers == null) - return; - - foreach(KeyValuePair> header in headers) - { - conn.SetRequestProperty(header.Key, header.Value != null ? String.Join(",", header.Value) : String.Empty); - } - } - - void SetupRequestBody(HttpURLConnection httpConnection, HttpRequestMessage request) - { - if(request.Content == null) - { - // Pilfered from System.Net.Http.HttpClientHandler:SendAync - if(HttpMethod.Post.Equals(request.Method) || HttpMethod.Put.Equals(request.Method) || HttpMethod.Delete.Equals(request.Method)) - { - // Explicitly set this to make sure we're sending a "Content-Length: 0" header. - // This fixes the issue that's been reported on the forums: - // http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release - httpConnection.SetRequestProperty("Content-Length", "0"); - } - return; - } - - httpConnection.DoOutput = true; - long? contentLength = request.Content.Headers.ContentLength; - if(contentLength != null) - httpConnection.SetFixedLengthStreamingMode((int)contentLength); - else - httpConnection.SetChunkedStreamingMode(0); - } - } - - sealed class AuthModuleBasic : IAndroidAuthenticationModule - { - public AuthenticationScheme Scheme { get; } = AuthenticationScheme.Basic; - public string AuthenticationType { get; } = "Basic"; - public bool CanPreAuthenticate { get; } = true; - - public Authorization Authenticate(string challenge, HttpURLConnection request, ICredentials credentials) - { - string header = challenge?.Trim(); - if(credentials == null || String.IsNullOrEmpty(header)) - return null; - - if(header.IndexOf("basic", StringComparison.OrdinalIgnoreCase) == -1) - return null; - - return InternalAuthenticate(request, credentials); - } - - public Authorization PreAuthenticate(HttpURLConnection request, ICredentials credentials) - { - return InternalAuthenticate(request, credentials); - } - - Authorization InternalAuthenticate(HttpURLConnection request, ICredentials credentials) - { - if(request == null || credentials == null) - return null; - - NetworkCredential cred = credentials.GetCredential(new Uri(request.URL.ToString()), AuthenticationType.ToLowerInvariant()); - if(cred == null) - return null; - - if(String.IsNullOrEmpty(cred.UserName)) - return null; - - string domain = cred.Domain?.Trim(); - string response = String.Empty; - - // If domain is set, MS sends "domain\user:password". - if(!String.IsNullOrEmpty(domain)) - response = domain + "\\"; - response += cred.UserName + ":" + cred.Password; - - return new Authorization($"{AuthenticationType} {Convert.ToBase64String(Encoding.ASCII.GetBytes(response))}"); - } - } -} \ No newline at end of file diff --git a/src/Android/Services/HttpService.cs b/src/Android/Services/HttpService.cs index 8879b70b4..a9c132ec6 100644 --- a/src/Android/Services/HttpService.cs +++ b/src/Android/Services/HttpService.cs @@ -7,6 +7,6 @@ namespace Bit.Android.Services { public class HttpService : IHttpService { - public ApiHttpClient Client => new ApiHttpClient(new CustomAndroidClientHandler()); + public ApiHttpClient Client => new ApiHttpClient(new AndroidClientHandler()); } }