Skip to content

Commit 1abd55a

Browse files
rassilonSreeshail1
authored andcommitted
Improvements in processing redirects with cookie containers.
1 parent fffe107 commit 1abd55a

File tree

1 file changed

+74
-6
lines changed

1 file changed

+74
-6
lines changed

src/RestSharp/RestClient.Async.cs

Lines changed: 74 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ async Task<HttpResponse> ExecuteRequestAsync(RestRequest request, CancellationTo
8989

9090
var httpMethod = AsHttpMethod(request.Method);
9191
var url = this.BuildUri(request);
92+
var originalUrl = url;
9293

9394
using var timeoutCts = new CancellationTokenSource(request.Timeout > 0 ? request.Timeout : int.MaxValue);
9495
using var cts = CancellationTokenSource.CreateLinkedTokenSource(timeoutCts.Token, cancellationToken);
@@ -122,11 +123,19 @@ async Task<HttpResponse> ExecuteRequestAsync(RestRequest request, CancellationTo
122123
.AddCookieHeaders(url, cookieContainer)
123124
.AddCookieHeaders(url, Options.CookieContainer);
124125

125-
HttpResponseMessage? responseMessage;
126+
bool foundCookies = false;
127+
HttpResponseMessage? responseMessage = null;
126128

127-
while (true) {
129+
do {
128130
using var requestContent = new RequestContent(this, request);
129-
using var message = PrepareRequestMessage(httpMethod, url, requestContent, headers);
131+
using var content = requestContent.BuildContent();
132+
133+
// If we found coookies during a redirect,
134+
// we need to update the Cookie headers:
135+
if (foundCookies) {
136+
headers.AddCookieHeaders(cookieContainer, url);
137+
}
138+
using var message = PrepareRequestMessage(httpMethod, url, content, headers);
130139

131140
if (request.OnBeforeRequest != null) await request.OnBeforeRequest(message).ConfigureAwait(false);
132141

@@ -149,19 +158,61 @@ async Task<HttpResponse> ExecuteRequestAsync(RestRequest request, CancellationTo
149158
location = new Uri(url, location);
150159
}
151160

161+
// Mirror HttpClient redirection behavior as of 07/25/2023:
162+
// Per https://tools.ietf.org/html/rfc7231#section-7.1.2, a redirect location without a
163+
// fragment should inherit the fragment from the original URI.
164+
string requestFragment = originalUrl.Fragment;
165+
if (!string.IsNullOrEmpty(requestFragment)) {
166+
string redirectFragment = location.Fragment;
167+
if (string.IsNullOrEmpty(redirectFragment)) {
168+
location = new UriBuilder(location) { Fragment = requestFragment }.Uri;
169+
}
170+
}
171+
172+
// Disallow automatic redirection from secure to non-secure schemes
173+
// From HttpClient's RedirectHandler:
174+
//if (HttpUtilities.IsSupportedSecureScheme(requestUri.Scheme) && !HttpUtilities.IsSupportedSecureScheme(location.Scheme)) {
175+
// if (NetEventSource.Log.IsEnabled()) {
176+
// TraceError($"Insecure https to http redirect from '{requestUri}' to '{location}' blocked.", response.RequestMessage!.GetHashCode());
177+
// }
178+
// break;
179+
//}
180+
152181
if (responseMessage.StatusCode == HttpStatusCode.RedirectMethod) {
153182
httpMethod = HttpMethod.Get;
154183
}
184+
// Based on Wikipedia https://en.wikipedia.org/wiki/HTTP_302:
185+
// Many web browsers implemented this code in a manner that violated this standard, changing
186+
// the request type of the new request to GET, regardless of the type employed in the original request
187+
// (e.g. POST). For this reason, HTTP/1.1 (RFC 2616) added the new status codes 303 and 307 to disambiguate
188+
// between the two behaviours, with 303 mandating the change of request type to GET, and 307 preserving the
189+
// request type as originally sent. Despite the greater clarity provided by this disambiguation, the 302 code
190+
// is still employed in web frameworks to preserve compatibility with browsers that do not implement the HTTP/1.1
191+
// specification.
192+
193+
// NOTE: Given the above, it is not surprising that HttpClient when AllowRedirect = true
194+
// solves this problem by a helper method:
195+
if (RedirectRequestRequiresForceGet(responseMessage.StatusCode, httpMethod)) {
196+
httpMethod = HttpMethod.Get;
197+
// HttpClient sets request.Content to null here:
198+
// TODO: However... should we be allowed to modify Request like that here?
199+
message.Content = null;
200+
// HttpClient Redirect handler also does this:
201+
//if (message.Headers.TansferEncodingChunked == true) {
202+
// request.Headers.TransferEncodingChunked = false;
203+
//}
204+
}
155205

156206
url = location;
157207

158208
if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var cookiesHeader)) {
209+
foundCookies = true;
159210
// ReSharper disable once PossibleMultipleEnumeration
160211
cookieContainer.AddCookies(url, cookiesHeader);
161212
// ReSharper disable once PossibleMultipleEnumeration
162213
Options.CookieContainer?.AddCookies(url, cookiesHeader);
163214
}
164-
}
215+
} while (true);
165216

166217
// Parse all the cookies from the response and update the cookie jar with cookies
167218
if (responseMessage.Headers.TryGetValues(KnownHeaders.SetCookie, out var cookiesHeader)) {
@@ -208,8 +259,25 @@ async Task OnAfterRequest(HttpResponseMessage responseMessage) {
208259
}
209260
}
210261

211-
HttpRequestMessage PrepareRequestMessage(HttpMethod httpMethod, Uri url, RequestContent requestContent, RequestHeaders headers) {
212-
var message = new HttpRequestMessage(httpMethod, url) { Content = requestContent.BuildContent() };
262+
/// <summary>
263+
/// Based on .net core RedirectHandler class:
264+
/// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/RedirectHandler.cs
265+
/// </summary>
266+
/// <param name="statusCode"></param>
267+
/// <param name="httpMethod"></param>
268+
/// <returns></returns>
269+
/// <exception cref="NotImplementedException"></exception>
270+
private bool RedirectRequestRequiresForceGet(HttpStatusCode statusCode, HttpMethod httpMethod) {
271+
return statusCode switch {
272+
HttpStatusCode.Moved or HttpStatusCode.Found or HttpStatusCode.MultipleChoices
273+
=> httpMethod == HttpMethod.Post,
274+
HttpStatusCode.SeeOther => httpMethod != HttpMethod.Get && httpMethod != HttpMethod.Head,
275+
_ => false,
276+
};
277+
}
278+
279+
HttpRequestMessage PrepareRequestMessage(HttpMethod httpMethod, Uri url, HttpContent content, RequestHeaders headers) {
280+
var message = new HttpRequestMessage(httpMethod, url) { Content = content };
213281
message.Headers.Host = Options.BaseHost;
214282
message.Headers.CacheControl = Options.CachePolicy;
215283
message.AddHeaders(headers);

0 commit comments

Comments
 (0)