Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improvements in processing redirects with cookie containers. #2119

Draft
wants to merge 24 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
164b45f
WIP
alexeyzimarev Apr 5, 2023
c6b39b5
Improvements in processing redirects with cookie containers.
rassilon Jul 25, 2023
00b718e
Added very first of many redirection cookie tests.
rassilon Jul 25, 2023
30eab16
Added very first of many redirection cookie tests.
rassilon Jul 25, 2023
9c56c06
Update previous redirect test case since, the redirected URL sends co…
rassilon Jul 25, 2023
1f9a142
Additional tests
rassilon Aug 29, 2023
d39f15c
more redirection/cookie related routes
rassilon Aug 29, 2023
653d0e0
Fix build error with respect to System.Web.HttpUtility.ParseQueryStri…
rassilon Sep 7, 2023
487f138
Add new files..
rassilon Sep 7, 2023
a0baaa6
FileParameter: Mark the obsoleted property as NOT CLSCompliant to red…
rassilon Sep 7, 2023
d3c7806
Try to pull in the correct assemblies for System.Web.HttpUtility.Pars…
rassilon Sep 7, 2023
28203df
Start paying attention to some of the Options.RedirectionOptions pro…
rassilon Sep 7, 2023
6557da7
Fix coding style violation..
rassilon Sep 7, 2023
eaae0b1
Improvements (and corrections on some tiny merge errors during) based…
rassilon Nov 2, 2023
9d8baf5
Fixes for Interceptor tests.
rassilon Nov 2, 2023
6764396
Start the process of testing the new RedirectOptions..
rassilon Nov 6, 2023
a69b48e
Update xunit adapter and visual studio test SDK nuget pkg versions fo…
rassilon Feb 28, 2024
88d141b
ForwardHeaders/ForwardAuthorization/ForwardCookies tests..
rassilon Mar 11, 2024
73df366
Add RedirectOptions.ForwardQuery support and tests.
rassilon Mar 12, 2024
aecb8bb
Fix Fragment handling with AddQueryString extension, and add ForwardF…
rassilon Mar 12, 2024
7b969f5
Add RedirectOptions.MaxRedirects support and tests.
rassilon Mar 12, 2024
950be06
Add secure test server end point to TestServer.
rassilon Mar 12, 2024
ee5e151
minor tweaks
rassilon Mar 12, 2024
4ca3453
RestClient.Async.cs: Convert lots of bools to a [Flag] enum setup.
rassilon Mar 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions src/RestSharp/KnownHeaders.cs
Expand Up @@ -36,6 +36,7 @@ public static class KnownHeaders {
public const string Cookie = "Cookie";
public const string SetCookie = "Set-Cookie";
public const string UserAgent = "User-Agent";
public const string TransferEncoding = "Transfer-Encoding";

internal static readonly string[] ContentHeaders = {
Allow, Expires, ContentDisposition, ContentEncoding, ContentLanguage, ContentLength, ContentLocation, ContentRange, ContentType, ContentMD5,
Expand Down
27 changes: 24 additions & 3 deletions src/RestSharp/Options/RestClientOptions.cs
Expand Up @@ -50,6 +50,12 @@ public class RestClientOptions {
/// <summary>
/// Custom configuration for the underlying <seealso cref="HttpMessageHandler"/>
/// </summary>
/// <remarks>
/// With the addition of all redirection processing being implemented directly by <see cref="RestClient"/>
/// please do not alter the <see cref="System.Net.Http.HttpClientHandler.AllowAutoRedirect"/> from its default supplied by RestClient.
/// If you set <see cref="System.Net.Http.HttpClientHandler.AllowAutoRedirect"/> to true, then redirection cookie
/// processing improvements in RestClient will be skipped since <see cref="System.Net.Http.HttpClient"/> will hide the details from us.
/// </remarks>
public Func<HttpMessageHandler, HttpMessageHandler>? ConfigureMessageHandler { get; set; }

/// <summary>
Expand All @@ -60,7 +66,7 @@ public class RestClientOptions {
? ResponseStatus.Completed
: ResponseStatus.Error;

/// <summary>
/// <summary>s
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this s needs to go?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Fixed locally.

/// Authenticator that will be used to populate request with necessary authentication data
/// </summary>
public IAuthenticator? Authenticator { get; set; }
Expand All @@ -86,7 +92,7 @@ public class RestClientOptions {
public bool UseDefaultCredentials { get; set; }

/// <summary>
/// Set to true if you need the Content-Type not to have the charset
/// Set to true if you need the Content-Type not to have the charset
/// </summary>
public bool DisableCharset { get; set; }

Expand Down Expand Up @@ -131,10 +137,25 @@ public class RestClientOptions {
/// </summary>
public CacheControlHeaderValue? CachePolicy { get; set; }

/// <summary>
/// Policy settings for redirect processing
/// </summary>
public RestClientRedirectionOptions RedirectOptions { get; set; } = new RestClientRedirectionOptions();

/// <summary>
/// Instruct the client to follow redirects. Default is true.
/// </summary>
public bool FollowRedirects { get; set; } = true;
/// <remarks>
/// Note: This now delegates the property implementation to <see cref="RestClientRedirectionOptions"/>.
/// </remarks>
public bool FollowRedirects {
get {
return RedirectOptions.FollowRedirects;
}
set {
RedirectOptions.FollowRedirects = value;
}
}

/// <summary>
/// Gets or sets a value that indicates if the <see langword="Expect" /> header for an HTTP request contains Continue.
Expand Down
143 changes: 143 additions & 0 deletions src/RestSharp/Options/RestClientRedirectionOptions.cs
@@ -0,0 +1,143 @@
using RestSharp.Extensions;
using System.Net;
using System.Reflection;

namespace RestSharp;

/// <summary>
/// Options related to redirect processing.
/// </summary>
[GenerateImmutable]
public class RestClientRedirectionOptions {
static readonly Version Version = new AssemblyName(typeof(RestClientOptions).Assembly.FullName!).Version!;

/// <summary>
/// Set to true (default), when you want to follow redirects
/// </summary>
public bool FollowRedirects { get; set; } = true;

/// <summary>
/// Set to true (default is false), when you want to follow a
/// redirect from HTTPS to HTTP.
/// </summary>
public bool FollowRedirectsToInsecure { get; set; } = false;

/// <summary>
/// Set to true (default), when you want to include the originally
/// requested headers in redirected requests.
/// </summary>
/// <remarks>NOTE: The 'Authorization' header is controlled by <see cref="ForwardAuthorization"/>,
/// and the 'Cookie' header is controlled by <see cref="ForwardCookies"/>.
/// </remarks>
public bool ForwardHeaders { get; set; } = true;

/// <summary>
/// Set to true (default is false), when you want to send the original
/// Authorization header to the redirected destination.
/// </summary>
public bool ForwardAuthorization { get; set; } = false;

/// <summary>
/// Set to true (default), when you want to include cookies from the
/// <see cref="CookieContainer"/> on the redirected URL.
/// </summary>
/// <remarks>
/// NOTE: The exact cookies sent to the redirected url DEPENDS directly
/// on the redirected url. A redirection to a completly differnet FQDN
/// for example is unlikely to actually propagate any cookies from the
/// <see cref="CookieContainer"/>.
/// </remarks>
public bool ForwardCookies { get; set; } = true;

/// <summary>
/// Set to true (default) in order to send the body to the
/// redirected URL, unless the force verb to GET behavior is triggered.
/// <see cref="ForceForwardBody"/>
/// </summary>
public bool ForwardBody { get; set; } = false;

/// <summary>
/// Set to true (default is false) to force forwarding the body of the
/// request even when normally, the verb might be altered to GET based
/// on backward compatiblity with browser processing of HTTP status codes.
/// </summary>
/// <remarks>
/// Based on Wikipedia https://en.wikipedia.org/wiki/HTTP_302:
/// <pre>
/// Many web browsers implemented this code in a manner that violated this standard, changing
/// the request type of the new request to GET, regardless of the type employed in the original request
/// (e.g. POST). For this reason, HTTP/1.1 (RFC 2616) added the new status codes 303 and 307 to disambiguate
/// between the two behaviours, with 303 mandating the change of request type to GET, and 307 preserving the
/// request type as originally sent. Despite the greater clarity provided by this disambiguation, the 302 code
/// is still employed in web frameworks to preserve compatibility with browsers that do not implement the HTTP/1.1
/// specification.
/// </pre>
/// </remarks>
public bool ForceForwardBody { get; set; } = false;

/// <summary>
/// Set to true (default) to forward the query string to the redirected URL.
/// </summary>
public bool ForwardQuery { get; set; } = true;

/// <summary>
/// The maximum number of redirects to follow.
/// </summary>
public int MaxRedirects { get; set; } = 50;

/// <summary>
/// Set to true (default), to supply any requested fragment portion of the original URL to the destination URL.
/// </summary>
/// <remarks>
/// Per https://tools.ietf.org/html/rfc7231#section-7.1.2, a redirect location without a
/// fragment should inherit the fragment from the original URI.
/// </remarks>
public bool ForwardFragment { get; set; } = true;

/// <summary>
/// Set to true (default), to allow the HTTP Method used on the original request to
/// be replaced with GET when the status code 303 (HttpStatusCode.RedirectMethod)
/// was returned. Setting this to false will disallow the altering of the verb.
/// </summary>
public bool AllowRedirectMethodStatusCodeToAlterVerb { get; set; } = true;

/// <summary>
/// Set to true (default), to allow the backward compatibility behavior of
/// changing the verb to GET with non 303 redirection status codes.
/// </summary>
/// <remarks>
/// NOTE: Even though the below text only references 302, this also allows some other scenarios.
/// See <see cref="RestClient.RedirectRequestRequiresForceGet(HttpStatusCode, HttpMethod)"/> for the specifics.
/// Based on Wikipedia https://en.wikipedia.org/wiki/HTTP_302:
/// Many web browsers implemented this code in a manner that violated this standard, changing
/// the request type of the new request to GET, regardless of the type employed in the original request
/// (e.g. POST). For this reason, HTTP/1.1 (RFC 2616) added the new status codes 303 and 307 to disambiguate
/// between the two behaviours, with 303 mandating the change of request type to GET, and 307 preserving the
/// request type as originally sent. Despite the greater clarity provided by this disambiguation, the 302 code
/// is still employed in web frameworks to preserve compatibility with browsers that do not implement the HTTP/1.1
/// specification.
/// </remarks>
public bool AllowForcedRedirectVerbChange { get; set; } = true;

/// <summary>
/// HttpStatusCodes that trigger redirect processing. Defaults to MovedPermanently (301),
/// SeeOther/RedirectMethod (303),
/// TemporaryRedirect (307),
/// Redirect (302),
/// PermanentRedirect (308)
/// </summary>
public IReadOnlyList<HttpStatusCode> RedirectStatusCodes { get; set; }

public RestClientRedirectionOptions() {
RedirectStatusCodes = new List<HttpStatusCode>() {
HttpStatusCode.MovedPermanently,
HttpStatusCode.SeeOther,
HttpStatusCode.TemporaryRedirect,
HttpStatusCode.Redirect,
#if NET
HttpStatusCode.PermanentRedirect,
#endif
}.AsReadOnly();
}
}

1 change: 1 addition & 0 deletions src/RestSharp/Parameters/FileParameter.cs
Expand Up @@ -114,6 +114,7 @@ public record FileParameter {
[PublicAPI]
public class FileParameterOptions {
[Obsolete("Use DisableFilenameStar instead")]
[CLSCompliant(false)]
public bool DisableFileNameStar {
get => DisableFilenameStar;
set => DisableFilenameStar = value;
Expand Down
12 changes: 6 additions & 6 deletions src/RestSharp/Request/RequestContent.cs
@@ -1,11 +1,11 @@
// Copyright (c) .NET Foundation and Contributors
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//
// http://www.apache.org/licenses/LICENSE-2.0
//
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand Down Expand Up @@ -36,7 +36,7 @@ class RequestContent : IDisposable {
_parameters = new RequestParameters(_request.Parameters.Union(_client.DefaultParameters));
}

public HttpContent BuildContent() {
public HttpContent BuildContent(bool omitBody = false) {
var postParameters = _parameters.GetContentParameters(_request.Method).ToArray();
var postParametersExists = postParameters.Length > 0;
var bodyParametersExists = _request.TryGetBodyParameter(out var bodyParameter);
Expand All @@ -51,7 +51,7 @@ class RequestContent : IDisposable {

if (filesExists) AddFiles();

if (bodyParametersExists) AddBody(postParametersExists, bodyParameter!);
if (bodyParametersExists && !omitBody) AddBody(postParametersExists, bodyParameter!);

if (postParametersExists) AddPostParameters(postParameters);

Expand Down Expand Up @@ -83,7 +83,7 @@ class RequestContent : IDisposable {
var dispositionHeader = fileParameter.Options.DisableFilenameEncoding
? ContentDispositionHeaderValue.Parse($"form-data; name=\"{fileParameter.Name}\"; filename=\"{fileParameter.FileName}\"")
: new ContentDispositionHeaderValue("form-data") { Name = $"\"{fileParameter.Name}\"", FileName = $"\"{fileParameter.FileName}\"" };
if (!fileParameter.Options.DisableFileNameStar) dispositionHeader.FileNameStar = fileParameter.FileName;
if (!fileParameter.Options.DisableFilenameStar) dispositionHeader.FileNameStar = fileParameter.FileName;
streamContent.Headers.ContentDisposition = dispositionHeader;

return streamContent;
Expand Down
15 changes: 11 additions & 4 deletions src/RestSharp/Request/UriExtensions.cs
Expand Up @@ -37,10 +37,17 @@ static class UriExtensions {
public static Uri AddQueryString(this Uri uri, string? query) {
if (query == null) return uri;

var absoluteUri = uri.AbsoluteUri;
var separator = absoluteUri.Contains('?') ? "&" : "?";

return new Uri($"{absoluteUri}{separator}{query}");
var absoluteUri = uri.AbsoluteUri;
var fragment = string.Empty;
if (!string.IsNullOrEmpty(uri.Fragment)) {
int fragmentStartIndex = absoluteUri.LastIndexOf(uri.Fragment);
if (fragmentStartIndex != -1) {
fragment = absoluteUri.Substring(fragmentStartIndex, absoluteUri.Length - fragmentStartIndex);
absoluteUri = absoluteUri.Substring(0, fragmentStartIndex);
}
}
var separator = string.IsNullOrEmpty(uri.Query) ? "?" : "&"; //absoluteUri.Contains('?') ? "&" : "?";
return new Uri($"{absoluteUri}{separator}{query}{fragment}");
}

public static UrlSegmentParamsValues GetUrlSegmentParamsValues(
Expand Down