diff --git a/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs b/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs index 16e0a3b1..e20c766a 100644 --- a/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs +++ b/src/Docker.DotNet.BasicAuth/BasicAuthCredentials.cs @@ -32,15 +32,20 @@ private BasicAuthCredentials(MaybeSecureString username, MaybeSecureString passw _password = password; } - public override bool IsTlsCredentials() - { - return _isTls; - } - public override void Dispose() { _username.Dispose(); _password.Dispose(); } + + public override bool SupportsScheme(string scheme) + { + return !(_isTls && scheme == "npipe"); + } + + public override bool IsTlsCredentials() + { + return _isTls; + } } -} \ No newline at end of file +} diff --git a/src/Docker.DotNet.X509/CertificateCredentials.cs b/src/Docker.DotNet.X509/CertificateCredentials.cs index 852bb34f..72ceb730 100644 --- a/src/Docker.DotNet.X509/CertificateCredentials.cs +++ b/src/Docker.DotNet.X509/CertificateCredentials.cs @@ -35,6 +35,11 @@ public override HttpMessageHandler GetHandler(HttpMessageHandler innerHandler) return handler; } + public override bool SupportsScheme(string scheme) + { + return scheme != "npipe"; + } + public override bool IsTlsCredentials() { return true; @@ -44,4 +49,4 @@ public override void Dispose() { } } -} \ No newline at end of file +} diff --git a/src/Docker.DotNet.X509/Docker.DotNet.X509.csproj b/src/Docker.DotNet.X509/Docker.DotNet.X509.csproj index b399186a..363839e3 100644 --- a/src/Docker.DotNet.X509/Docker.DotNet.X509.csproj +++ b/src/Docker.DotNet.X509/Docker.DotNet.X509.csproj @@ -8,4 +8,4 @@ - \ No newline at end of file + diff --git a/src/Docker.DotNet/AnonymousCredentials.cs b/src/Docker.DotNet/AnonymousCredentials.cs index 08495359..f1e54004 100644 --- a/src/Docker.DotNet/AnonymousCredentials.cs +++ b/src/Docker.DotNet/AnonymousCredentials.cs @@ -9,6 +9,11 @@ public override bool IsTlsCredentials() return false; } + public override bool SupportsScheme(string scheme) + { + return true; + } + public override void Dispose() { } @@ -18,4 +23,4 @@ public override HttpMessageHandler GetHandler(HttpMessageHandler innerHandler) return innerHandler; } } -} \ No newline at end of file +} diff --git a/src/Docker.DotNet/Credentials.cs b/src/Docker.DotNet/Credentials.cs index 3619f0c9..6c136e9d 100644 --- a/src/Docker.DotNet/Credentials.cs +++ b/src/Docker.DotNet/Credentials.cs @@ -5,6 +5,8 @@ namespace Docker.DotNet { public abstract class Credentials : IDisposable { + public abstract bool SupportsScheme(string scheme); + public abstract bool IsTlsCredentials(); public abstract HttpMessageHandler GetHandler(HttpMessageHandler innerHandler); @@ -13,4 +15,4 @@ public virtual void Dispose() { } } -} \ No newline at end of file +} diff --git a/src/Docker.DotNet/DockerClient.cs b/src/Docker.DotNet/DockerClient.cs index f04e24ee..c967555c 100644 --- a/src/Docker.DotNet/DockerClient.cs +++ b/src/Docker.DotNet/DockerClient.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Pipes; using System.Linq; using System.Net; using System.Net.Http; @@ -47,78 +46,8 @@ internal DockerClient(DockerClientConfiguration configuration, Version requested Plugin = new PluginOperations(this); Exec = new ExecOperations(this); - ManagedHandler handler; - var uri = Configuration.EndpointBaseUri; - switch (uri.Scheme.ToLowerInvariant()) - { - case "npipe": - if (Configuration.Credentials.IsTlsCredentials()) - { - throw new Exception("TLS not supported over npipe"); - } - - var segments = uri.Segments; - if (segments.Length != 3 || !segments[1].Equals("pipe/", StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException($"{Configuration.EndpointBaseUri} is not a valid npipe URI"); - } - - var serverName = uri.Host; - if (string.Equals(serverName, "localhost", StringComparison.OrdinalIgnoreCase)) - { - // npipe schemes dont work with npipe://localhost/... and need npipe://./... so fix that for a client here. - serverName = "."; - } - - var pipeName = uri.Segments[2]; - - uri = new UriBuilder("http", pipeName).Uri; - handler = new ManagedHandler(async (host, port, cancellationToken) => - { - var timeout = (int)Configuration.NamedPipeConnectTimeout.TotalMilliseconds; - var stream = new NamedPipeClientStream(serverName, pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); - var dockerStream = new DockerPipeStream(stream); - - await stream.ConnectAsync(timeout, cancellationToken) - .ConfigureAwait(false); - - return dockerStream; - }); - break; - - case "tcp": - case "http": - var builder = new UriBuilder(uri) - { - Scheme = configuration.Credentials.IsTlsCredentials() ? "https" : "http" - }; - uri = builder.Uri; - handler = new ManagedHandler(); - break; - - case "https": - handler = new ManagedHandler(); - break; - - case "unix": - var pipeString = uri.LocalPath; - handler = new ManagedHandler(async (host, port, cancellationToken) => - { - var sock = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); - - await sock.ConnectAsync(new Microsoft.Net.Http.Client.UnixDomainSocketEndPoint(pipeString)) - .ConfigureAwait(false); - - return sock; - }); - uri = new UriBuilder("http", uri.Segments.Last()).Uri; - break; - - default: - throw new Exception($"Unknown URL scheme {configuration.EndpointBaseUri.Scheme}"); - } - - _endpointBaseUri = uri; + var (url, handler) = Configuration.GetHandler(); + _endpointBaseUri = url; _client = new HttpClient(Configuration.Credentials.GetHandler(handler), true); _client.Timeout = SInfiniteTimeout; diff --git a/src/Docker.DotNet/DockerClientConfiguration.cs b/src/Docker.DotNet/DockerClientConfiguration.cs index 39937a7c..188b0bcc 100644 --- a/src/Docker.DotNet/DockerClientConfiguration.cs +++ b/src/Docker.DotNet/DockerClientConfiguration.cs @@ -1,7 +1,11 @@ using System; using System.Collections.Generic; +using System.IO.Pipes; +using System.Linq; +using System.Net.Sockets; using System.Runtime.InteropServices; using System.Threading; +using Microsoft.Net.Http.Client; namespace Docker.DotNet { @@ -63,10 +67,87 @@ public void Dispose() Credentials.Dispose(); } + public virtual (Uri url, ManagedHandler handler) GetHandler() + { + if (!Credentials.SupportsScheme(EndpointBaseUri.Scheme)) + { + throw new Exception($"The provided credentials don't support the {EndpointBaseUri.Scheme} scheme."); + } + + var uri = EndpointBaseUri; + ManagedHandler handler; + + switch (EndpointBaseUri.Scheme.ToLowerInvariant()) + { + case "npipe": + var segments = uri.Segments; + if (segments.Length != 3 || !segments[1].Equals("pipe/", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException($"{uri} is not a valid npipe URI"); + } + + var serverName = uri.Host; + if (string.Equals(serverName, "localhost", StringComparison.OrdinalIgnoreCase)) + { + // npipe schemes dont work with npipe://localhost/... and need npipe://./... so fix that for a client here. + serverName = "."; + } + + var pipeName = uri.Segments[2]; + + uri = new UriBuilder("http", pipeName).Uri; + handler = new ManagedHandler(async (host, port, cancellationToken) => + { + var timeout = (int)NamedPipeConnectTimeout.TotalMilliseconds; + var stream = new NamedPipeClientStream(serverName, pipeName, PipeDirection.InOut, PipeOptions.Asynchronous); + var dockerStream = new DockerPipeStream(stream); + + await stream.ConnectAsync(timeout, cancellationToken) + .ConfigureAwait(false); + + return dockerStream; + }); + break; + + case "tcp": + case "http": + var builder = new UriBuilder(uri) + { + Scheme = Credentials.IsTlsCredentials() ? "https" : "http" + }; + uri = builder.Uri; + handler = new ManagedHandler(); + break; + + case "https": + handler = new ManagedHandler(); + break; + + case "unix": + var pipeString = uri.LocalPath; + handler = new ManagedHandler(async (host, port, cancellationToken) => + { + var sock = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified); + + await sock.ConnectAsync(new Microsoft.Net.Http.Client.UnixDomainSocketEndPoint(pipeString)) + .ConfigureAwait(false); + + return sock; + }); + uri = new UriBuilder("http", uri.Segments.Last()).Uri; + break; + + default: + throw new Exception($"URL scheme {EndpointBaseUri.Scheme} is unsupported by this implementation."); + } + + return (uri, handler); + } + private static Uri GetLocalDockerEndpoint() { var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); return isWindows ? new Uri("npipe://./pipe/docker_engine") : new Uri("unix:/var/run/docker.sock"); } } -} \ No newline at end of file +} diff --git a/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs b/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs index 86f68264..29df8f37 100644 --- a/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs +++ b/src/Docker.DotNet/Microsoft.Net.Http.Client/HttpConnection.cs @@ -106,6 +106,7 @@ private async Task> ReadResponseLinesAsync(CancellationToken cancel private HttpResponseMessage CreateResponseMessage(List responseLines) { string responseLine = responseLines.First(); + // HTTP/1.1 200 OK string[] responseLineParts = responseLine.Split(new[] { ' ' }, 3); // TODO: Verify HTTP/1.0 or 1.1.