Skip to content

Commit

Permalink
[cookie_manager] Fix FileSystemException when saving redirect cooki…
Browse files Browse the repository at this point in the history
…es without a proper `host` (#1948)

Upon relative redirect, cookies were not saved because the `host` was
missing. `resolveUri` provides the proper host info for a redirect Uri
in such cases and doesn't affect others.

Signed-off-by: Alex Isaienko <[email protected]>
Co-authored-by: Alex Li <[email protected]>
  • Loading branch information
s0nerik and AlexV525 authored Oct 1, 2023
1 parent 4d7ea68 commit 57a6577
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 2 deletions.
2 changes: 1 addition & 1 deletion plugins/cookie_manager/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Unreleased

*None.*
- Fix `FileSystemException` when saving redirect cookies without a proper `host`.

## 3.1.0+1

Expand Down
5 changes: 4 additions & 1 deletion plugins/cookie_manager/lib/src/cookie_mgr.dart
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,10 @@ class CookieManager extends Interceptor {
// users will be available to handle cookies themselves.
final isRedirectRequest = statusCode >= 300 && statusCode < 400;
// Saving cookies for the original site.
await cookieJar.saveFromResponse(response.realUri, cookies);
// Spec: https://www.rfc-editor.org/rfc/rfc7231#section-7.1.2.
final originalUri = response.requestOptions.uri;
final realUri = originalUri.resolveUri(response.realUri);
await cookieJar.saveFromResponse(realUri, cookies);
if (isRedirectRequest && locations.isNotEmpty) {
final originalUri = response.realUri;
await Future.wait(
Expand Down
153 changes: 153 additions & 0 deletions plugins/cookie_manager/test/cookies_persistance_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import 'dart:collection';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:dio/io.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:test/fake.dart';
import 'package:test/test.dart';

typedef _FetchCallback = Future<ResponseBody> Function(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future<void>? cancelFuture,
);

class _TestAdapter implements HttpClientAdapter {
_TestAdapter({required _FetchCallback fetch}) : _fetch = fetch;

final _FetchCallback _fetch;
final HttpClientAdapter _adapter = IOHttpClientAdapter();

@override
Future<ResponseBody> fetch(
RequestOptions options,
Stream<Uint8List>? requestStream,
Future<void>? cancelFuture,
) =>
_fetch(options, requestStream, cancelFuture);

@override
void close({bool force = false}) {
_adapter.close(force: force);
}
}

class _SaveCall {
_SaveCall(this.uri, this.cookies);

final String uri;
final String cookies;

@override
bool operator ==(Object other) =>
identical(this, other) ||
other is _SaveCall &&
runtimeType == other.runtimeType &&
uri == other.uri &&
cookies == other.cookies;

@override
int get hashCode => uri.hashCode ^ cookies.hashCode;

@override
String toString() {
return '_SaveCall{uri: $uri, cookies: $cookies}';
}
}

class _FakeCookieJar extends Fake implements CookieJar {
final _saveCalls = <_SaveCall>[];

List<_SaveCall> get saveCalls => UnmodifiableListView(_saveCalls);

@override
Future<List<Cookie>> loadForRequest(Uri uri) async {
return const [];
}

@override
Future<void> saveFromResponse(Uri uri, List<Cookie> cookies) async {
_saveCalls.add(_SaveCall(
uri.toString(),
cookies.join('; '),
));
}
}

void main() {
group('CookieJar.saveFromResponse()', () {
test(
'is called with a full Uri for requests that had relative redirects',
() async {
final cookieJar = _FakeCookieJar();
final dio = Dio()
..httpClientAdapter = _TestAdapter(
fetch: (options, requestStream, cancelFuture) async => ResponseBody(
Stream.value(Uint8List.fromList(utf8.encode(''))),
HttpStatus.ok,
redirects: [
RedirectRecord(
HttpStatus.found,
'GET',
Uri(path: 'redirect'),
),
],
headers: {
HttpHeaders.setCookieHeader: ['Cookie1=value1; Path=/'],
},
),
)
..interceptors.add(CookieManager(cookieJar))
..options.validateStatus =
(status) => status != null && status >= 200 && status < 400;

await dio.get('https://test.com');
expect(cookieJar.saveCalls, [
_SaveCall(
'https://test.com/redirect',
'Cookie1=value1; Path=/',
),
]);
},
);

test(
'saves cookies only for final destination upon non-relative redirects',
() async {
final cookieJar = _FakeCookieJar();
final dio = Dio()
..httpClientAdapter = _TestAdapter(
fetch: (options, requestStream, cancelFuture) async => ResponseBody(
Stream.value(Uint8List.fromList(utf8.encode(''))),
HttpStatus.ok,
redirects: [
RedirectRecord(
HttpStatus.found,
'GET',
Uri.parse('https://example.com/redirect'),
),
],
headers: {
HttpHeaders.setCookieHeader: ['Cookie1=value1; Path=/'],
},
),
)
..interceptors.add(CookieManager(cookieJar))
..options.validateStatus =
(status) => status != null && status >= 200 && status < 400;

await dio.get('https://test.com');
expect(cookieJar.saveCalls, [
_SaveCall(
'https://example.com/redirect',
'Cookie1=value1; Path=/',
),
]);
},
);
});
}

0 comments on commit 57a6577

Please sign in to comment.