|
18 | 18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
19 | 19 | */ |
20 | 20 | import 'dart:async'; |
21 | | -import 'dart:convert'; |
22 | 21 | import 'dart:ui'; |
23 | 22 |
|
24 | | -import 'package:collection/collection.dart'; |
25 | 23 | import 'package:exceptions/exceptions.dart'; |
26 | 24 | import 'package:flutter/foundation.dart'; |
27 | 25 | import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; |
28 | 26 | import 'package:html/dom.dart'; |
29 | 27 | import 'package:html/parser.dart' as html_parser; |
| 28 | +import 'package:http/io_client.dart'; |
30 | 29 | import 'package:nullability/nullability.dart'; |
31 | 30 | import 'package:riverpod_annotation/riverpod_annotation.dart'; |
| 31 | +import 'package:socks5_proxy/socks_client.dart'; |
| 32 | +import 'package:universal_io/io.dart'; |
32 | 33 | import 'package:weblibre/core/http_error_handler.dart'; |
33 | | -import 'package:weblibre/core/logger.dart'; |
34 | 34 | import 'package:weblibre/data/models/web_page_info.dart'; |
| 35 | +import 'package:weblibre/extensions/http_encoding.dart'; |
35 | 36 | import 'package:weblibre/features/geckoview/domain/entities/browser_icon.dart'; |
36 | 37 | import 'package:weblibre/features/user/domain/repositories/cache.dart'; |
37 | 38 | import 'package:weblibre/features/web_feed/utils/feed_finder.dart'; |
@@ -210,42 +211,54 @@ class GenericWebsiteService extends _$GenericWebsiteService { |
210 | 211 | Future<Result<WebPageInfo>> fetchPageInfo({ |
211 | 212 | required Uri url, |
212 | 213 | required bool isImageRequest, |
| 214 | + required int? proxyPort, |
213 | 215 | }) { |
214 | 216 | return Result.fromAsync(() async { |
215 | | - late final Map<String, Object?> result; |
216 | | - final client = GeckoFetchService(); |
217 | | - try { |
218 | | - final response = await client |
219 | | - .fetch(url: url) |
220 | | - .timeout(const Duration(seconds: 15)); |
221 | | - |
222 | | - //When this is a request for an icon and we hit an image, directly return it |
223 | | - if (isImageRequest) { |
224 | | - final contentType = response.headers |
225 | | - .firstWhereOrNull((header) => header.key == 'content-type') |
226 | | - ?.value; |
227 | | - if (contentType?.contains('image/') == true) { |
228 | | - result = { |
229 | | - 'imageBytes': [response.body], |
230 | | - }; |
231 | | - } |
| 217 | + final result = await compute((args) async { |
| 218 | + final [String urlString, bool isImageRequest, int? proxyPort] = args; |
| 219 | + |
| 220 | + final httpClient = HttpClient(); |
| 221 | + if (proxyPort != null) { |
| 222 | + SocksTCPClient.assignToHttpClient(httpClient, [ |
| 223 | + ProxySettings(InternetAddress.loopbackIPv4, proxyPort), |
| 224 | + ]); |
232 | 225 | } |
233 | 226 |
|
234 | | - final document = html_parser.parse(utf8.decode(response.body)); |
235 | | - |
236 | | - final title = document.querySelector('title')?.text; |
237 | | - final resources = _extractIcons(url, document); |
238 | | - final feeds = await FeedFinder(url: url, document: document).parse(); |
| 227 | + final client = IOClient(httpClient); |
| 228 | + try { |
| 229 | + final baseUri = Uri.parse(urlString); |
| 230 | + final response = await client |
| 231 | + .get(baseUri) |
| 232 | + .timeout(const Duration(seconds: 15)); |
| 233 | + |
| 234 | + //When this is a request for an icon and we hit an image, directly return it |
| 235 | + if (isImageRequest) { |
| 236 | + final contentType = response.headers['content-type']; |
| 237 | + if (contentType?.contains('image/') == true) { |
| 238 | + return { |
| 239 | + 'imageBytes': [response.bodyBytes], |
| 240 | + }; |
| 241 | + } |
| 242 | + } |
239 | 243 |
|
240 | | - result = { |
241 | | - 'title': title, |
242 | | - 'resources': resources.map(_serializeResource).toList(), |
243 | | - 'feeds': feeds.map((uri) => uri.toString()).toList(), |
244 | | - }; |
245 | | - } catch (e, s) { |
246 | | - logger.e('Error fetching page $url', error: e, stackTrace: s); |
247 | | - result = {}; |
248 | | - } |
| 244 | + final document = html_parser.parse(response.bodyUnicodeFallback); |
| 245 | + |
| 246 | + final title = document.querySelector('title')?.text; |
| 247 | + final resources = _extractIcons(baseUri, document); |
| 248 | + final feeds = await FeedFinder( |
| 249 | + url: baseUri, |
| 250 | + document: document, |
| 251 | + ).parse(); |
| 252 | + |
| 253 | + return { |
| 254 | + 'title': title, |
| 255 | + 'resources': resources.map(_serializeResource).toList(), |
| 256 | + 'feeds': feeds.map((uri) => uri.toString()).toList(), |
| 257 | + }; |
| 258 | + } finally { |
| 259 | + client.close(); |
| 260 | + } |
| 261 | + }, <dynamic>[url.toString(), isImageRequest, proxyPort]); |
249 | 262 |
|
250 | 263 | if (result['imageBytes'] case final Uint8List imageBytes) { |
251 | 264 | return WebPageInfo( |
|
0 commit comments