Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 2c32a92

Browse files
committed
Credential Support in Unix HTTP Handler
This checkin integrates CurlHandler to the credential support of libcurl
1 parent 1f79ba7 commit 2c32a92

File tree

5 files changed

+163
-13
lines changed

5 files changed

+163
-13
lines changed

src/Common/src/Interop/Unix/libcurl/Interop.libcurl.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,12 @@ public static extern int curl_easy_setopt(
9898
int option,
9999
long value);
100100

101+
[DllImport(Interop.Libraries.LibCurl)]
102+
public static extern int curl_easy_setopt(
103+
SafeCurlHandle curl,
104+
int option,
105+
ulong value);
106+
101107
[DllImport(Interop.Libraries.LibCurl)]
102108
public static extern int curl_easy_setopt(
103109
SafeCurlHandle curl,
@@ -132,6 +138,12 @@ public static extern int curl_easy_getinfo(
132138
int info,
133139
out IntPtr value);
134140

141+
[DllImport(Interop.Libraries.LibCurl)]
142+
public static extern int curl_easy_getinfo(
143+
SafeCurlHandle handle,
144+
int info,
145+
out ulong value);
146+
135147
[DllImport(Interop.Libraries.LibCurl, CharSet = CharSet.Ansi)]
136148
public static extern IntPtr curl_slist_append(
137149
IntPtr curl_slist,

src/Common/src/Interop/Unix/libcurl/Interop.libcurl_types.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ internal static partial class CURLoption
3333
internal const int CURLOPT_PROXYPORT = CurlOptionLongBase + 59;
3434
internal const int CURLOPT_MAXREDIRS = CurlOptionLongBase + 68;
3535
internal const int CURLOPT_PROXYTYPE = CurlOptionLongBase + 101;
36+
internal const int CURLOPT_HTTPAUTH = CurlOptionLongBase + 107;
3637

3738
internal const int CURLOPT_WRITEDATA = CurlOptionObjectPointBase + 1;
3839
internal const int CURLOPT_URL = CurlOptionObjectPointBase + 2;
@@ -45,6 +46,8 @@ internal static partial class CURLoption
4546
internal const int CURLOPT_ACCEPTENCODING = CurlOptionObjectPointBase + 102;
4647
internal const int CURLOPT_PRIVATE = CurlOptionObjectPointBase + 103;
4748
internal const int CURLOPT_IOCTLDATA = CurlOptionObjectPointBase + 131;
49+
internal const int CURLOPT_USERNAME = CurlOptionObjectPointBase + 173;
50+
internal const int CURLOPT_PASSWORD = CurlOptionObjectPointBase + 174;
4851

4952
internal const int CURLOPT_WRITEFUNCTION = CurlOptionFunctionPointBase + 11;
5053
internal const int CURLOPT_READFUNCTION = CurlOptionFunctionPointBase + 12;
@@ -70,8 +73,10 @@ internal static partial class CURLINFO
7073
{
7174
// Curl info are of the format <type base> + <n>
7275
private const int CurlInfoStringBase = 0x100000;
76+
private const int CurlInfoLongBase = 0x200000;
7377

7478
internal const int CURLINFO_PRIVATE = CurlInfoStringBase + 21;
79+
internal const int CURLINFO_HTTPAUTH_AVAIL = CurlInfoLongBase + 23;
7580
}
7681

7782
// Class for constants defined for the enum curl_proxytype in curl.h
@@ -125,6 +130,17 @@ internal static partial class CURLMSG
125130
internal const int CURLMSG_DONE = 1;
126131
}
127132

133+
// AUTH related constants
134+
internal static partial class CURLAUTH
135+
{
136+
internal const ulong None = 0;
137+
internal const ulong Basic = 1 << 0;
138+
internal const ulong Digest = 1 << 1;
139+
internal const ulong Negotiate = 1 << 2;
140+
internal const ulong DigestIE = 1 << 4;
141+
internal const ulong AuthAny = ~DigestIE;
142+
}
143+
128144
internal static partial class CURL_VERSION_Features
129145
{
130146
internal const int CURL_VERSION_IPV6 = (1<<0);

src/System.Net.Http/src/System/Net/Http/Unix/CurlCallbacks.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
using curl_socket_t = System.Int32;
1818
using CurlPoll = Interop.libcurl.CurlPoll;
1919
using CurlSelect = Interop.libcurl.CurlSelect;
20+
using CURLAUTH = Interop.libcurl.CURLAUTH;
21+
using CURLoption = Interop.libcurl.CURLoption;
2022

2123
namespace System.Net.Http
2224
{
@@ -57,7 +59,7 @@ private static size_t CurlReceiveHeadersCallback(
5759
HttpResponseMessage response = state.ResponseMessage;
5860

5961
// TODO: Understand scenarios where multiple sets of headers are received
60-
if (!TryParseStatusLine(response, responseHeader))
62+
if (!TryParseStatusLine(response, responseHeader, state))
6163
{
6264
int colonIndex = responseHeader.IndexOf(':');
6365

@@ -198,7 +200,7 @@ private static size_t ExecuteCallback(
198200
}
199201
}
200202

201-
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader)
203+
private static bool TryParseStatusLine(HttpResponseMessage response, string responseHeader, RequestCompletionSource state)
202204
{
203205
if (!responseHeader.StartsWith(s_httpPrefix, StringComparison.OrdinalIgnoreCase))
204206
{
@@ -250,6 +252,17 @@ private static bool TryParseStatusLine(HttpResponseMessage response, string resp
250252
{
251253
response.StatusCode = (HttpStatusCode)statusCode;
252254

255+
// For security reasons, we drop the server credential if it is a
256+
// NetworkCredential. But we allow credentials in a CredentialCache
257+
// since they are specifically tied to URI's.
258+
if ((response.StatusCode == HttpStatusCode.Redirect) && !(state.Handler.Credentials is CredentialCache))
259+
{
260+
state.Handler.SetCurlOption(state.RequestHandle, CURLoption.CURLOPT_HTTPAUTH, CURLAUTH.None);
261+
state.Handler.SetCurlOption(state.RequestHandle, CURLoption.CURLOPT_USERNAME, IntPtr.Zero );
262+
state.Handler.SetCurlOption(state.RequestHandle, CURLoption.CURLOPT_PASSWORD, IntPtr.Zero);
263+
state.NetworkCredential = null;
264+
}
265+
253266
int codeEndIndex = codeStartIndex + StatusCodeLength;
254267

255268
int reasonPhraseIndex = codeEndIndex + 1;

src/System.Net.Http/src/System/Net/Http/Unix/CurlHandler.cs

Lines changed: 118 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
using CURLcode = Interop.libcurl.CURLcode;
2020
using CURLMcode = Interop.libcurl.CURLMcode;
2121
using CURLINFO = Interop.libcurl.CURLINFO;
22+
using CURLAUTH = Interop.libcurl.CURLAUTH;
2223
using CurlVersionInfoData = Interop.libcurl.curl_version_info_data;
2324
using CurlFeatures = Interop.libcurl.CURL_VERSION_Features;
2425
using CURLProxyType = Interop.libcurl.curl_proxytype;
@@ -36,6 +37,7 @@ internal partial class CurlHandler : HttpMessageHandler
3637
private const string EncodingNameGzip = "gzip";
3738
private const string EncodingNameDeflate = "deflate";
3839
private readonly static string[] AuthenticationSchemes = { "Negotiate", "Digest", "Basic" }; // the order in which libcurl goes over authentication schemes
40+
private readonly static ulong[] AuthSchemePriorityOrder = { CURLAUTH.Negotiate, CURLAUTH.Digest, CURLAUTH.Basic };
3941
private static readonly string[] s_headerDelimiters = new string[] { "\r\n" };
4042

4143
private const int s_requestBufferSize = 16384; // Default used by libcurl
@@ -58,6 +60,8 @@ internal partial class CurlHandler : HttpMessageHandler
5860
private DecompressionMethods _automaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
5961
private SafeCurlMultiHandle _multiHandle;
6062
private GCHandle _multiHandlePtr = new GCHandle();
63+
private bool _preAuthenticate = false;
64+
private CredentialCache _credentialCache = null;
6165
private CookieContainer _cookieContainer = null;
6266
private bool _useCookie = false;
6367
private bool _automaticRedirection = true;
@@ -159,7 +163,6 @@ internal ICredentials Credentials
159163

160164
set
161165
{
162-
CheckDisposedOrStarted();
163166
_serverCredentials = value;
164167
}
165168
}
@@ -202,6 +205,23 @@ internal DecompressionMethods AutomaticDecompression
202205
}
203206
}
204207

208+
internal bool PreAuthenticate
209+
{
210+
get
211+
{
212+
return _preAuthenticate;
213+
}
214+
set
215+
{
216+
CheckDisposedOrStarted();
217+
_preAuthenticate = value;
218+
if (value)
219+
{
220+
_credentialCache = new CredentialCache();
221+
}
222+
}
223+
}
224+
205225
internal bool UseCookie
206226
{
207227
get
@@ -306,14 +326,13 @@ protected internal override Task<HttpResponseMessage> SendAsync(
306326
}
307327

308328
// Create RequestCompletionSource object and save current values of handler settings.
309-
RequestCompletionSource state = new RequestCompletionSource
329+
RequestCompletionSource state = new RequestCompletionSource(this)
310330
{
311331
CancellationToken = cancellationToken,
312332
RequestMessage = request,
313333
};
314334

315335
BeginRequest(state);
316-
317336
return state.Task;
318337
}
319338

@@ -398,6 +417,17 @@ private static void EndRequest(SafeCurlMultiHandle multiHandle, IntPtr statePtr,
398417
state.TrySetException(new HttpRequestException(SR.net_http_client_execution_error,
399418
GetCurlException(result)));
400419
}
420+
421+
if (state.ResponseMessage.StatusCode != HttpStatusCode.Unauthorized && state.Handler.PreAuthenticate)
422+
{
423+
ulong availedAuth;
424+
if (Interop.libcurl.curl_easy_getinfo(state.RequestHandle, CURLINFO.CURLINFO_HTTPAUTH_AVAIL, out availedAuth) == CURLcode.CURLE_OK)
425+
{
426+
state.Handler.AddCredentialToCache(state.RequestMessage.RequestUri, availedAuth, state.NetworkCredential);
427+
}
428+
// ignoring the exception in this case.
429+
// There is no point in killing the request for the sake of putting the credentials into the cache
430+
}
401431
}
402432
catch (Exception ex)
403433
{
@@ -455,6 +485,8 @@ private SafeCurlHandle CreateRequestHandle(RequestCompletionSource state, GCHand
455485

456486
SetProxyOptions(requestHandle, state.RequestMessage.RequestUri);
457487

488+
SetRequestHandleCredentialsOptions(requestHandle, state);
489+
458490
SetCookieOption(requestHandle, state.RequestMessage.RequestUri);
459491

460492
state.RequestHeaderHandle = SetRequestHeaders(requestHandle, state.RequestMessage);
@@ -528,6 +560,44 @@ private void SetProxyOptions(SafeCurlHandle requestHandle, Uri requestUri)
528560
}
529561
}
530562

563+
private void SetRequestHandleCredentialsOptions(SafeCurlHandle requestHandle, RequestCompletionSource state)
564+
{
565+
NetworkCredential credentials = GetNetworkCredentials(state.Handler._serverCredentials, state.RequestMessage.RequestUri);
566+
if (credentials != null)
567+
{
568+
string userName = string.IsNullOrEmpty(credentials.Domain) ?
569+
credentials.UserName :
570+
string.Format("{0}\\{1}", credentials.Domain, credentials.UserName);
571+
572+
SetCurlOption(requestHandle, CURLoption.CURLOPT_USERNAME, userName);
573+
SetCurlOption(requestHandle, CURLoption.CURLOPT_HTTPAUTH, CURLAUTH.AuthAny);
574+
if (credentials.Password != null)
575+
{
576+
SetCurlOption(requestHandle, CURLoption.CURLOPT_PASSWORD, credentials.Password);
577+
}
578+
579+
state.NetworkCredential = credentials;
580+
}
581+
}
582+
583+
private NetworkCredential GetNetworkCredentials(ICredentials credentials, Uri requestUri)
584+
{
585+
if (_preAuthenticate)
586+
{
587+
NetworkCredential nc = null;
588+
lock (_multiHandle)
589+
{
590+
nc = GetCredentials(_credentialCache, requestUri);
591+
}
592+
if (nc != null)
593+
{
594+
return nc;
595+
}
596+
}
597+
598+
return GetCredentials(credentials, requestUri);
599+
}
600+
531601
private void SetCookieOption(SafeCurlHandle requestHandle, Uri requestUri)
532602
{
533603
if (!_useCookie)
@@ -547,22 +617,35 @@ private void SetCookieOption(SafeCurlHandle requestHandle, Uri requestUri)
547617
}
548618
}
549619

550-
private NetworkCredential GetCredentials(ICredentials proxyCredentials, Uri requestUri)
620+
private void AddCredentialToCache(Uri serverUri, ulong authAvail, NetworkCredential nc)
621+
{
622+
lock (_multiHandle)
623+
{
624+
for (int i=0; i < AuthSchemePriorityOrder.Length; i++)
625+
{
626+
if ((authAvail & AuthSchemePriorityOrder[i]) != 0 )
627+
{
628+
_credentialCache.Add(serverUri, AuthenticationSchemes[i], nc);
629+
}
630+
}
631+
}
632+
}
633+
634+
private static NetworkCredential GetCredentials(ICredentials credentials, Uri requestUri)
551635
{
552-
if (proxyCredentials == null)
636+
if (credentials == null)
553637
{
554638
return null;
555639
}
556640

557641
foreach (var authScheme in AuthenticationSchemes)
558642
{
559-
NetworkCredential proxyCreds = proxyCredentials.GetCredential(requestUri, authScheme);
560-
if (proxyCreds != null)
643+
NetworkCredential networkCredential = credentials.GetCredential(requestUri, authScheme);
644+
if (networkCredential != null)
561645
{
562-
return proxyCreds;
646+
return networkCredential;
563647
}
564648
}
565-
566649
return null;
567650
}
568651

@@ -660,6 +743,15 @@ private void SetCurlOption(SafeCurlHandle handle, int option, long value)
660743
}
661744
}
662745

746+
private void SetCurlOption(SafeCurlHandle handle, int option, ulong value)
747+
{
748+
int result = Interop.libcurl.curl_easy_setopt(handle, option, value);
749+
if (result != CURLcode.CURLE_OK)
750+
{
751+
throw new HttpRequestException(SR.net_http_client_execution_error, GetCurlException(result));
752+
}
753+
}
754+
663755
private void SetCurlOption(SafeCurlHandle handle, int option, Interop.libcurl.curl_readwrite_callback value)
664756
{
665757
int result = Interop.libcurl.curl_easy_setopt(handle, option, value);
@@ -868,6 +960,13 @@ private static void RemoveEasyHandle(SafeCurlMultiHandle multiHandle, GCHandle s
868960

869961
private sealed class RequestCompletionSource : TaskCompletionSource<HttpResponseMessage>
870962
{
963+
private readonly CurlHandler _handler;
964+
965+
public RequestCompletionSource(CurlHandler handler)
966+
{
967+
this._handler = handler;
968+
}
969+
871970
public CancellationToken CancellationToken { get; set; }
872971

873972
public HttpRequestMessage RequestMessage { get; set; }
@@ -883,6 +982,16 @@ private sealed class RequestCompletionSource : TaskCompletionSource<HttpResponse
883982
public Stream RequestContentStream { get; set; }
884983

885984
public byte[] RequestContentBuffer { get; set; }
985+
986+
public NetworkCredential NetworkCredential {get; set;}
987+
988+
public CurlHandler Handler
989+
{
990+
get
991+
{
992+
return _handler;
993+
}
994+
}
886995
}
887996

888997
private enum ProxyUsePolicy

src/System.Net.Http/src/System/Net/Http/Unix/HttpClientHandler.Unix.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,8 @@ public IWebProxy Proxy
8484

8585
public bool PreAuthenticate
8686
{
87-
get { throw NotImplemented.ByDesignWithMessage("HTTP stack not implemented"); }
88-
set { throw NotImplemented.ByDesignWithMessage("HTTP stack not implemented"); }
87+
get { return _curlHandler.PreAuthenticate; }
88+
set { _curlHandler.PreAuthenticate = value;}
8989
}
9090

9191
public bool UseDefaultCredentials

0 commit comments

Comments
 (0)