main
dart 126 lines 3.93 KB
Raw
1 import 'package:flutter_riverpod/flutter_riverpod.dart';
2 import 'package:dio/dio.dart';
3 import '../constants/app_constants.dart';
4 import '../../features/servers/data/server_api_service.dart';
5
6 final dioProvider = Provider<Dio>((ref) {
7 final dio = Dio(
8 BaseOptions(
9 connectTimeout: Duration(milliseconds: AppConstants.connectionTimeoutMs),
10 receiveTimeout: Duration(milliseconds: AppConstants.receiveTimeoutMs),
11 ),
12 );
13 dio.interceptors.add(RedirectInterceptor(dio));
14 return dio;
15 });
16
17 final serverApiServiceProvider = Provider<ServerApiService>((ref) {
18 return ServerApiService(ref.read(dioProvider));
19 });
20
21 class RedirectInterceptor extends Interceptor {
22 final Dio _dio;
23
24 RedirectInterceptor(this._dio);
25
26 @override
27 void onResponse(Response response, ResponseInterceptorHandler handler) async {
28 final statusCode = response.statusCode;
29 if (statusCode == 307 || statusCode == 308) {
30 final location = response.headers.value('location');
31 if (location != null) {
32 try {
33 final redirectedResponse = await _performRedirect(
34 response.requestOptions,
35 location,
36 );
37 return handler.resolve(redirectedResponse);
38 } catch (_) {
39 return handler.next(response);
40 }
41 }
42 }
43 super.onResponse(response, handler);
44 }
45
46 @override
47 void onError(DioException err, ErrorInterceptorHandler handler) async {
48 final response = err.response;
49 if (response != null &&
50 (response.statusCode == 307 || response.statusCode == 308)) {
51 final location = response.headers.value('location');
52 if (location != null) {
53 try {
54 final redirectedResponse = await _performRedirect(
55 err.requestOptions,
56 location,
57 );
58 return handler.resolve(redirectedResponse);
59 } catch (e) {
60 if (e is DioException) {
61 return handler.next(e);
62 }
63 return handler.next(
64 DioException(
65 requestOptions: err.requestOptions,
66 error: e,
67 type: DioExceptionType.unknown,
68 ),
69 );
70 }
71 }
72 }
73 super.onError(err, handler);
74 }
75
76 Future<Response<dynamic>> _performRedirect(
77 RequestOptions requestOptions,
78 String location,
79 ) async {
80 var redirectUrl = location;
81 final originalUri = requestOptions.uri;
82 if (!redirectUrl.startsWith('http://') &&
83 !redirectUrl.startsWith('https://')) {
84 final baseUri = Uri.parse(requestOptions.path);
85 redirectUrl = baseUri.resolve(redirectUrl).toString();
86 }
87
88 final extra = Map<String, dynamic>.from(requestOptions.extra);
89 final redirectCount = (extra['redirectCount'] as int? ?? 0) + 1;
90 if (redirectCount > 5) {
91 throw DioException(
92 requestOptions: requestOptions,
93 error: 'Too many redirects',
94 type: DioExceptionType.unknown,
95 );
96 }
97 extra['redirectCount'] = redirectCount;
98
99 // Never replay credentials (API keys, auth tokens) to a different
100 // origin than the one the caller actually configured — a redirect
101 // response (e.g. from a compromised or misconfigured server) must not
102 // be able to exfiltrate them to an arbitrary host.
103 final redirectUri = Uri.parse(redirectUrl);
104 final sameOrigin =
105 redirectUri.scheme == originalUri.scheme &&
106 redirectUri.host == originalUri.host &&
107 redirectUri.port == originalUri.port;
108 final headers = Map<String, dynamic>.from(requestOptions.headers);
109 if (!sameOrigin) {
110 headers.removeWhere((key, _) => key.toLowerCase() == 'authorization');
111 }
112
113 return _dio.request(
114 redirectUrl,
115 data: requestOptions.data,
116 queryParameters: requestOptions.queryParameters,
117 options: Options(
118 method: requestOptions.method,
119 headers: headers,
120 contentType: requestOptions.contentType,
121 responseType: requestOptions.responseType,
122 extra: extra,
123 ),
124 );
125 }
126 }