From 350a08b64fe0ee33a6abf39620c83a2e9c9ffba0 Mon Sep 17 00:00:00 2001 From: Alex Isaienko Date: Thu, 31 Aug 2023 22:58:31 +0300 Subject: [PATCH 1/9] Fixes `FileSystemException: Cannot open file ... (OS Error: Is a directory, errno = 21)` --- plugins/cookie_manager/lib/src/cookie_mgr.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/cookie_manager/lib/src/cookie_mgr.dart b/plugins/cookie_manager/lib/src/cookie_mgr.dart index 271926a27..d09378566 100644 --- a/plugins/cookie_manager/lib/src/cookie_mgr.dart +++ b/plugins/cookie_manager/lib/src/cookie_mgr.dart @@ -123,7 +123,9 @@ 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); + if (response.realUri.isNotEmpty) { + await cookieJar.saveFromResponse(response.realUri, cookies); + } if (isRedirectRequest && locations.isNotEmpty) { final originalUri = response.realUri; await Future.wait( From 4ec39c34c012cc2dbc654f7939dcc32443847ed5 Mon Sep 17 00:00:00 2001 From: Alex Isaienko Date: Thu, 31 Aug 2023 23:11:41 +0300 Subject: [PATCH 2/9] Fixes `FileSystemException: Cannot open file ... (OS Error: Is a directory, errno = 21)` --- plugins/cookie_manager/lib/src/cookie_mgr.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cookie_manager/lib/src/cookie_mgr.dart b/plugins/cookie_manager/lib/src/cookie_mgr.dart index d09378566..7a2b6a0e1 100644 --- a/plugins/cookie_manager/lib/src/cookie_mgr.dart +++ b/plugins/cookie_manager/lib/src/cookie_mgr.dart @@ -123,7 +123,7 @@ class CookieManager extends Interceptor { // users will be available to handle cookies themselves. final isRedirectRequest = statusCode >= 300 && statusCode < 400; // Saving cookies for the original site. - if (response.realUri.isNotEmpty) { + if (response.realUri.toString().isNotEmpty) { await cookieJar.saveFromResponse(response.realUri, cookies); } if (isRedirectRequest && locations.isNotEmpty) { From 2220c263dce2e6c80b67676eae5082dede03c127 Mon Sep 17 00:00:00 2001 From: Alex Isaienko Date: Fri, 1 Sep 2023 00:33:06 +0300 Subject: [PATCH 3/9] Fixes `FileSystemException: Cannot open file ... (OS Error: Is a directory, errno = 21)` --- plugins/cookie_manager/lib/src/cookie_mgr.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/cookie_manager/lib/src/cookie_mgr.dart b/plugins/cookie_manager/lib/src/cookie_mgr.dart index 7a2b6a0e1..b469feaeb 100644 --- a/plugins/cookie_manager/lib/src/cookie_mgr.dart +++ b/plugins/cookie_manager/lib/src/cookie_mgr.dart @@ -123,9 +123,9 @@ class CookieManager extends Interceptor { // users will be available to handle cookies themselves. final isRedirectRequest = statusCode >= 300 && statusCode < 400; // Saving cookies for the original site. - if (response.realUri.toString().isNotEmpty) { - await cookieJar.saveFromResponse(response.realUri, cookies); - } + 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( From 10755a918144e842d1a81de62e48175e80650e1b Mon Sep 17 00:00:00 2001 From: Alex Isaienko Date: Fri, 1 Sep 2023 12:29:56 +0300 Subject: [PATCH 4/9] Added a test to verify that CookieJar.saveFromResponse() is called with a full Uri for requests that had relative redirects --- .../test/cookies_persistance_test.dart | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 plugins/cookie_manager/test/cookies_persistance_test.dart diff --git a/plugins/cookie_manager/test/cookies_persistance_test.dart b/plugins/cookie_manager/test/cookies_persistance_test.dart new file mode 100644 index 000000000..e1d651071 --- /dev/null +++ b/plugins/cookie_manager/test/cookies_persistance_test.dart @@ -0,0 +1,115 @@ +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 Function( + RequestOptions options, + Stream? requestStream, + Future? cancelFuture, +); + +class _TestAdapter implements HttpClientAdapter { + _TestAdapter({required _FetchCallback fetch}) : _fetch = fetch; + + final _FetchCallback _fetch; + final HttpClientAdapter _adapter = IOHttpClientAdapter(); + + @override + Future fetch( + RequestOptions options, + Stream? requestStream, + Future? 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> loadForRequest(Uri uri) async { + return const []; + } + + @override + Future saveFromResponse(Uri uri, List cookies) async { + _saveCalls.add(_SaveCall( + uri.toString(), + cookies.join('; '), + )); + } +} + +void main() { + test( + 'CookieJar.saveFromResponse() 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=/', + ), + ]); + }); +} From 326e1c2bff104d2ee9864398fb28420beabfba16 Mon Sep 17 00:00:00 2001 From: Alex Isaienko Date: Fri, 1 Sep 2023 15:33:15 +0300 Subject: [PATCH 5/9] Updated CHANGELOG.md --- plugins/cookie_manager/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cookie_manager/CHANGELOG.md b/plugins/cookie_manager/CHANGELOG.md index 7050c1fd4..c536b2615 100644 --- a/plugins/cookie_manager/CHANGELOG.md +++ b/plugins/cookie_manager/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -*None.* +- Fix `FileSystemException: Cannot open file ... (OS Error: Is a directory, errno = 21)` ## 3.1.0+1 From b81020a612368a6d764e629305c65d600d5ec24b Mon Sep 17 00:00:00 2001 From: Alex Isaienko Date: Fri, 1 Sep 2023 22:09:10 +0300 Subject: [PATCH 6/9] Added another test for `CookieJar.saveFromResponse()` --- .../test/cookies_persistance_test.dart | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/plugins/cookie_manager/test/cookies_persistance_test.dart b/plugins/cookie_manager/test/cookies_persistance_test.dart index e1d651071..d7562bba6 100644 --- a/plugins/cookie_manager/test/cookies_persistance_test.dart +++ b/plugins/cookie_manager/test/cookies_persistance_test.dart @@ -112,4 +112,38 @@ void main() { ), ]); }); + test( + 'CookieJar.saveFromResponse() 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=/', + ), + ]); + }); } From c26840cde91ac5090c5511daadc54466d7cd7acc Mon Sep 17 00:00:00 2001 From: Alex Isaienko Date: Fri, 29 Sep 2023 21:55:15 +0300 Subject: [PATCH 7/9] Update CHANGELOG.md Co-authored-by: Alex Li Signed-off-by: Alex Isaienko --- plugins/cookie_manager/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cookie_manager/CHANGELOG.md b/plugins/cookie_manager/CHANGELOG.md index c536b2615..f12ee4f43 100644 --- a/plugins/cookie_manager/CHANGELOG.md +++ b/plugins/cookie_manager/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -- Fix `FileSystemException: Cannot open file ... (OS Error: Is a directory, errno = 21)` +- Fix `FileSystemException` when saving redirect cookies without a proper `host`. ## 3.1.0+1 From af40b43f0ca1e5f537b2a4a121047d7fc784ba5e Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 1 Oct 2023 13:58:57 +0800 Subject: [PATCH 8/9] =?UTF-8?q?=F0=9F=93=9D=20Add=20spec=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- plugins/cookie_manager/lib/src/cookie_mgr.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/cookie_manager/lib/src/cookie_mgr.dart b/plugins/cookie_manager/lib/src/cookie_mgr.dart index b469feaeb..9004ed5d3 100644 --- a/plugins/cookie_manager/lib/src/cookie_mgr.dart +++ b/plugins/cookie_manager/lib/src/cookie_mgr.dart @@ -123,6 +123,7 @@ class CookieManager extends Interceptor { // users will be available to handle cookies themselves. final isRedirectRequest = statusCode >= 300 && statusCode < 400; // Saving cookies for the original site. + // 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); From 2c4db02ae1e43e03bf6bcbc8502b587a72173e1a Mon Sep 17 00:00:00 2001 From: Alex Li Date: Sun, 1 Oct 2023 14:04:59 +0800 Subject: [PATCH 9/9] =?UTF-8?q?=E2=9C=85=20Group=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../test/cookies_persistance_test.dart | 130 +++++++++--------- 1 file changed, 67 insertions(+), 63 deletions(-) diff --git a/plugins/cookie_manager/test/cookies_persistance_test.dart b/plugins/cookie_manager/test/cookies_persistance_test.dart index d7562bba6..2ab50e13d 100644 --- a/plugins/cookie_manager/test/cookies_persistance_test.dart +++ b/plugins/cookie_manager/test/cookies_persistance_test.dart @@ -61,6 +61,7 @@ class _SaveCall { class _FakeCookieJar extends Fake implements CookieJar { final _saveCalls = <_SaveCall>[]; + List<_SaveCall> get saveCalls => UnmodifiableListView(_saveCalls); @override @@ -78,72 +79,75 @@ class _FakeCookieJar extends Fake implements CookieJar { } void main() { - test( - 'CookieJar.saveFromResponse() is called with a full Uri for requests that had relative redirects', + 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'), + 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=/'], + }, ), - ], - 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( - 'CookieJar.saveFromResponse() saves cookies only for final destination upon non-relative redirects', + ) + ..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'), + 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=/'], + }, ), - ], - 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=/', - ), - ]); + ) + ..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=/', + ), + ]); + }, + ); }); }