@@ -164,18 +164,38 @@ public async Task<Response> Send(HttpMethod method, string path, Dictionary<stri
164164 request = await authStrategy . AuthenticateRequest ( request ) . ConfigureAwait ( false ) ;
165165 }
166166
167- var response = await Send ( request ) . ConfigureAwait ( false ) ;
167+ Response response ;
168168
169- // If we get unauthorized response, try to authenticate a second time.
170- // This is common in OAuth when an access token is expired.
171- if ( ( response . StatusCode == HttpStatusCode . Unauthorized || response . StatusCode == HttpStatusCode . Forbidden ) && authStrategy is IRefreshableAuthStrategy refreshableAuthStrategy )
169+ try
172170 {
173- // we need to build a new request here; requests can only be sent once
174- request . Dispose ( ) ;
171+ response = await Send ( request ) . ConfigureAwait ( false ) ;
172+
173+ // If we get unauthorized response, try to authenticate a second time.
174+ // This is common in OAuth when an access token is expired.
175+ if ( ( response . StatusCode == HttpStatusCode . Unauthorized || response . StatusCode == HttpStatusCode . Forbidden ) && authStrategy is IRefreshableAuthStrategy refreshableAuthStrategy )
176+ {
177+ // we need to build a new request here; requests can only be sent once
178+ request . Dispose ( ) ;
175179#pragma warning disable CA2000 // Dispose objects before losing scope
176- request = PrepareRequestMessage ( method , url , headers , body ) ;
180+ request = PrepareRequestMessage ( method , url , headers , body ) ;
177181#pragma warning restore CA2000 // Dispose objects before losing scope
178- request = await refreshableAuthStrategy . RefreshCredentialsAndAuthenticateRequest ( request ) . ConfigureAwait ( false ) ;
182+ request = await refreshableAuthStrategy . RefreshCredentialsAndAuthenticateRequest ( request ) . ConfigureAwait ( false ) ;
183+ response = await Send ( request ) . ConfigureAwait ( false ) ;
184+ }
185+ }
186+ catch ( RequestRedirectedException ex )
187+ {
188+ request . Dispose ( ) ;
189+
190+ // recreate a new request with the redirected url.
191+ request = PrepareRequestMessage ( method , ex . RedirectUri , headers , body ) ;
192+
193+ if ( authStrategy != null )
194+ {
195+ // Send the first authenticated request
196+ request = await authStrategy . AuthenticateRequest ( request ) . ConfigureAwait ( false ) ;
197+ }
198+
179199 response = await Send ( request ) . ConfigureAwait ( false ) ;
180200 }
181201
@@ -280,9 +300,24 @@ async Task<Response> Send(HttpRequestMessage request)
280300
281301 try
282302 {
303+ var originalUri = request . RequestUri ;
304+
283305 var httpResponse = await webClient . SendAsync ( request ) . ConfigureAwait ( false ) ;
284306 var responseContent = await httpResponse . Content . ReadAsStringAsync ( ) . ConfigureAwait ( false ) ;
285307
308+ // Verify if a redirect happened.
309+ if ( httpResponse . StatusCode == HttpStatusCode . Moved
310+ || httpResponse . StatusCode == HttpStatusCode . MovedPermanently )
311+ {
312+ var redirectUrl = httpResponse . Headers . Location ;
313+
314+ if ( redirectUrl . Host == request . RequestUri . Host
315+ && redirectUrl . AbsolutePath != request . RequestUri ? . AbsolutePath )
316+ {
317+ throw new RequestRedirectedException ( redirectUrl ) ;
318+ }
319+ }
320+
286321 return new Response ( httpResponse , responseContent ) ;
287322 }
288323 catch ( Exception e )
@@ -295,5 +330,18 @@ async Task<Response> Send(HttpRequestMessage request)
295330 throw ;
296331 }
297332 }
333+
334+ /// <summary>
335+ /// Request redirected exception helper to handle a content redirect.
336+ /// </summary>
337+ internal class RequestRedirectedException : Exception
338+ {
339+ public RequestRedirectedException ( Uri redirectUri )
340+ {
341+ RedirectUri = redirectUri ;
342+ }
343+
344+ public Uri RedirectUri { get ; }
345+ }
298346 }
299347}
0 commit comments