@@ -1976,6 +1976,162 @@ void asio_client::send_request(const std::shared_ptr<request_context>& request_c
19761976 ctx->start_request ();
19771977}
19781978
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+
19792135pplx::task<http_response> asio_client::propagate (http_request request)
19802136{
19812137 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)
19952151 // Asynchronously send the response with the HTTP client implementation.
19962152 this ->async_send_request (context);
19972153
1998- return result_task;
2154+ return client_config ().max_redirects () > 0
2155+ ? result_task.then (http_redirect_follower (client_config (), request))
2156+ : result_task;
19992157}
20002158} // namespace details
20012159} // namespace client
0 commit comments