dio_redirect_interceptor 2.0.0
dio_redirect_interceptor: ^2.0.0 copied to clipboard
A custom interceptor for Dio HTTP client to handle redirects manually.
2.0.0 #
Security and correctness release. The redirect request is now built and dispatched differently, so behaviour changes for existing users — see below.
Security #
- Credential headers (
authorization,cookie,proxy-authorization) are no longer forwarded when a redirect crosses to another origin. Previously the headers on theDioinstance were sent to whatever host theLocationheader named, so an open redirect on the server — or a tampered response — leaked the token. SetstripCredentialsOnCrossOrigin: falseto restore the old behaviour. - An
httpsrequest is no longer redirected tohttpby default. PassallowProtocolDowngrade: trueto permit it. - Redirects to schemes other than
httpandhttpsare refused. Locationis resolved with RFC 3986 reference resolution. The previouslocation.contains("://")test treated any value containing://anywhere as an absolute URL, soLocation: /login?next=https://example.com/was returned verbatim and produced a request against an unintended target.- A response carrying more than one
Locationheader is refused instead of silently following a folded value.
Added #
RequestOptions.redirectContext, holding aRedirectContexton every redirected request, so an interceptor that attaches credentials can tell which hop it is running on and skip signing it. It exposesoriginalUri,previousUri,currentUri,count, and three ways of asking about the origin —hasLeftOriginalOrigin(sticky once the chain leaves the origin the caller asked for, the conservative choice for a credential),hopCrossedOrigin(this hop alone, the rule browsers apply), andisOnOriginalOrigin(the return leg of ana → identity provider → alogin).RequestOptions.rawUriandRequestOptions.redirectCountextensions.
Fixed #
-
An exception raised while inspecting a response — from a
RedirectCallback, aRedirectValidator, or a non-boolvalue underextra['followRedirects']— used to leave the request future hanging forever, because dio discards the future a response interceptor returns and waits on the handler alone. All of it now runs inside the guard and fails the request instead. -
A
FormDatabody is cloned before being replayed on a 307/308.FormDatafinalises itself on first use, so a redirected multipart upload previously failed withStateError: The FormData has already been finalized. -
A 303 no longer forwards the payload when it happens to leave the method alone, as RFC 9110 requires.
-
The redirected request carries a mutable
queryParametersmap, so an interceptor that writes into it — an api-key or signing interceptor — no longer throwsUnsupportedErrorfrom the second hop onwards. -
transformTimeoutis carried across hops. -
content-encoding,content-md5,transfer-encodingandexpectare dropped along with the body, not justcontent-lengthandcontent-type. -
The final response is handed to the interceptors that follow this one exactly once. It used to be forwarded again at every nesting level, so a logging or caching interceptor registered after this one fired three times for a two-hop redirect chain.
-
Errors synthesized by this interceptor — a refused downgrade, a malformed or duplicated
Location, the hop limit — are delivered to the error interceptors that follow it. An error raised by the nested request is not re-delivered, since it already traversed the chain once. -
The method and body of the original request are preserved across 307 and 308 redirects. Every redirect previously became a bodyless
GET. 303 turns everything butHEADintoGET, and 301/302 rewrite onlyPOST, matching browsers and curl. -
The headers of the original request are carried across the redirect. They were dropped entirely before, leaving only the headers of the
Dioinstance. -
CancelToken,onSendProgressandonReceiveProgressare carried across the redirect, so cancelling a redirected request works. -
Errors raised while handling a redirect are surfaced as
DioExceptionwith the cause inerror, instead of escaping as a bare exception. -
Following a redirect no longer mutates the
extramap of the options the caller passed in, so a reusedOptionsno longer accumulatesredirectCount. -
followRedirectsandvalidateStatusno longer have to be configured by hand. The interceptor setsfollowRedirects: falseon the requests it handles so the adapter cannot swallow a hop, and widens avalidateStatusthat would reject redirect statuses — otherwise forcing the first would turn every redirect into abadResponseerror. A redirect that is not followed (pastmaxRedirectCount, or declined byRedirectValidator) is still judged by the caller's ownvalidateStatus. AvalidateStatusthat already accepts 301, 302, 303, 307 and 308 is left untouched.
Known limitations #
- Each hop re-enters the whole interceptor chain, so
onRequestof every interceptor runs once per hop, and interceptors registered after this one never observe the 3xx hops. See "Interceptor ordering" in the README. - Cross-origin credential stripping only covers the headers this interceptor
carries. Another interceptor that injects credentials in
onRequestruns again on each hop and would re-add them; guard it withoptions.redirectContext.
1.0.1 #
- Update dart version to ">=3.0.0 <4.0.0"
1.0.0 #
- Initial version.