@@ -1976,6 +1976,162 @@ void asio_client::send_request(const std::shared_ptr<request_context>& request_c
1976
1976
ctx->start_request ();
1977
1977
}
1978
1978
1979
+ static bool is_retrieval_redirection (status_code code)
1980
+ {
1981
+ // See https://tools.ietf.org/html/rfc7231#section-6.4
1982
+
1983
+ switch (code)
1984
+ {
1985
+ case status_codes::MovedPermanently:
1986
+ // "For historical reasons, a user agent MAY change the request method
1987
+ // from POST to GET for the subsequent request."
1988
+ return true ;
1989
+ case status_codes::Found:
1990
+ // "For historical reasons, a user agent MAY change the request method
1991
+ // from POST to GET for the subsequent request."
1992
+ return true ;
1993
+ case status_codes::SeeOther:
1994
+ // "A user agent can perform a [GET or HEAD] request. It is primarily
1995
+ // used to allow the output of a POST action to redirect the user agent
1996
+ // to a selected resource."
1997
+ return true ;
1998
+ default :
1999
+ return false ;
2000
+ }
2001
+ }
2002
+
2003
+ static bool is_unchanged_redirection (status_code code)
2004
+ {
2005
+ // See https://tools.ietf.org/html/rfc7231#section-6.4
2006
+ // and https://tools.ietf.org/html/rfc7538#section-3
2007
+
2008
+ switch (code)
2009
+ {
2010
+ case status_codes::TemporaryRedirect:
2011
+ // "The user agent MUST NOT change the request method if it performs an
2012
+ // automatic redirection to that URI."
2013
+ return true ;
2014
+ case status_codes::PermanentRedirect:
2015
+ // This status code "does not allow changing the request method from POST
2016
+ // to GET."
2017
+ return true ;
2018
+ default :
2019
+ return false ;
2020
+ }
2021
+ }
2022
+
2023
+ static bool is_recognized_redirection (status_code code)
2024
+ {
2025
+ // other 3xx status codes, e.g. 300 Multiple Choices, are not handled
2026
+ // and should be handled externally
2027
+ return is_retrieval_redirection (code) || is_unchanged_redirection (code);
2028
+ }
2029
+
2030
+ static bool is_retrieval_request (method method)
2031
+ {
2032
+ return methods::GET == method || methods::HEAD == method;
2033
+ }
2034
+
2035
+ static const std::vector<utility::string_t > request_body_header_names =
2036
+ {
2037
+ header_names::content_encoding,
2038
+ header_names::content_language,
2039
+ header_names::content_length,
2040
+ header_names::content_location,
2041
+ header_names::content_type
2042
+ };
2043
+
2044
+ // A request continuation that follows redirects according to the specified configuration.
2045
+ // This implementation only supports retrieval redirects, as it cannot redirect e.g. a POST request
2046
+ // using the same method since the request body may have been consumed.
2047
+ struct http_redirect_follower
2048
+ {
2049
+ http_client_config config;
2050
+ std::vector<uri> followed_urls;
2051
+ http_request redirect;
2052
+
2053
+ http_redirect_follower (http_client_config config, const http_request& request);
2054
+
2055
+ uri url_to_follow (const http_response& response) const ;
2056
+
2057
+ pplx::task<http_response> operator ()(http_response response);
2058
+ };
2059
+
2060
+ http_redirect_follower::http_redirect_follower (http_client_config config, const http_request& request)
2061
+ : config(std::move(config))
2062
+ , followed_urls(1 , request.absolute_uri())
2063
+ , redirect(request.method())
2064
+ {
2065
+ // Stash the original request URL, etc. to be prepared for an automatic redirect
2066
+
2067
+ // Basically, it makes sense to send the redirects with the same headers as the original request
2068
+ redirect.headers () = request.headers ();
2069
+ // However, this implementation only supports retrieval redirects, with no body, so Content-* headers
2070
+ // should be removed
2071
+ for (const auto & content_header : request_body_header_names)
2072
+ {
2073
+ redirect.headers ().remove (content_header);
2074
+ }
2075
+
2076
+ redirect._set_cancellation_token (request._cancellation_token ());
2077
+ }
2078
+
2079
+ uri http_redirect_follower::url_to_follow (const http_response& response) const
2080
+ {
2081
+ // Return immediately if the response is not a supported redirection
2082
+ if (!is_recognized_redirection (response.status_code ()))
2083
+ return {};
2084
+
2085
+ // Although not required by RFC 7231, config may limit the number of automatic redirects
2086
+ // (followed_urls includes the initial request URL, hence '<' here)
2087
+ if (config.max_redirects () < followed_urls.size ())
2088
+ return {};
2089
+
2090
+ // Can't very well automatically redirect if the server hasn't provided a Location
2091
+ const auto location = response.headers ().find (header_names::location);
2092
+ if (response.headers ().end () == location)
2093
+ return {};
2094
+
2095
+ uri to_follow (followed_urls.back ().resolve_uri (location->second ));
2096
+
2097
+ // Config may prohibit automatic redirects from HTTPS to HTTP
2098
+ if (!config.https_to_http_redirects () && followed_urls.back ().scheme () == _XPLATSTR (" https" )
2099
+ && to_follow.scheme () != _XPLATSTR (" https" ))
2100
+ return {};
2101
+
2102
+ // "A client SHOULD detect and intervene in cyclical redirections."
2103
+ if (followed_urls.end () != std::find (followed_urls.begin (), followed_urls.end (), to_follow))
2104
+ return {};
2105
+
2106
+ return to_follow;
2107
+ }
2108
+
2109
+ pplx::task<http_response> http_redirect_follower::operator ()(http_response response)
2110
+ {
2111
+ // Return immediately if the response doesn't indicate a valid automatic redirect
2112
+ uri to_follow = url_to_follow (response);
2113
+ if (to_follow.is_empty ())
2114
+ return pplx::task_from_result (response);
2115
+
2116
+ // This implementation only supports retrieval redirects, as it cannot redirect e.g. a POST request
2117
+ // using the same method since the request body may have been consumed.
2118
+ if (!is_retrieval_request (redirect.method ()) && !is_retrieval_redirection (response.status_code ()))
2119
+ return pplx::task_from_result (response);
2120
+
2121
+ if (!is_retrieval_request (redirect.method ()))
2122
+ redirect.set_method (methods::GET);
2123
+
2124
+ // If the reply to this request is also a redirect, we want visibility of that
2125
+ auto config_no_redirects = config;
2126
+ config_no_redirects.set_max_redirects (0 );
2127
+ http_client client (to_follow, config_no_redirects);
2128
+
2129
+ // Stash the redirect request URL and make the request with the same continuation
2130
+ auto request_task = client.request (redirect, redirect._cancellation_token ());
2131
+ followed_urls.push_back (std::move (to_follow));
2132
+ return request_task.then (std::move (*this ));
2133
+ }
2134
+
1979
2135
pplx::task<http_response> asio_client::propagate (http_request request)
1980
2136
{
1981
2137
auto self = std::static_pointer_cast<_http_client_communicator>(shared_from_this ());
@@ -1995,7 +2151,9 @@ pplx::task<http_response> asio_client::propagate(http_request request)
1995
2151
// Asynchronously send the response with the HTTP client implementation.
1996
2152
this ->async_send_request (context);
1997
2153
1998
- return result_task;
2154
+ return client_config ().max_redirects () > 0
2155
+ ? result_task.then (http_redirect_follower (client_config (), request))
2156
+ : result_task;
1999
2157
}
2000
2158
} // namespace details
2001
2159
} // namespace client
0 commit comments