Skip to content

Commit 10634e8

Browse files
authored
Merge pull request #2225 from ImranR98/dev
- Ensure headers are still sent when URL request is redirected (#1973) - Add 'ETag header' option for HTML and direct APK links (#2221) - Ensure links on add app page do not overlap/merge (#2216)
2 parents 5dc8461 + 2317c5e commit 10634e8

File tree

8 files changed

+124
-43
lines changed

8 files changed

+124
-43
lines changed

lib/app_sources/directAPKLink.dart

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'package:easy_localization/easy_localization.dart';
22
import 'package:obtainium/app_sources/html.dart';
3+
import 'package:obtainium/components/generated_form.dart';
34
import 'package:obtainium/custom_errors.dart';
45
import 'package:obtainium/providers/source_provider.dart';
56

@@ -8,12 +9,23 @@ class DirectAPKLink extends AppSource {
89

910
DirectAPKLink() {
1011
name = tr('directAPKLink');
11-
additionalSourceAppSpecificSettingFormItems = html
12-
.additionalSourceAppSpecificSettingFormItems
13-
.where((element) => element
14-
.where((element) => element.key == 'requestHeader')
15-
.isNotEmpty)
16-
.toList();
12+
additionalSourceAppSpecificSettingFormItems = [
13+
...html.additionalSourceAppSpecificSettingFormItems
14+
.where((element) => element
15+
.where((element) => element.key == 'requestHeader')
16+
.isNotEmpty)
17+
.toList(),
18+
[
19+
GeneratedFormDropdown(
20+
'defaultPseudoVersioningMethod',
21+
[
22+
MapEntry('partialAPKHash', tr('partialAPKHash')),
23+
MapEntry('ETag', 'ETag')
24+
],
25+
label: tr('defaultPseudoVersioningMethod'),
26+
defaultValue: 'partialAPKHash')
27+
]
28+
];
1729
excludeCommonSettingKeys = [
1830
'versionExtractionRegEx',
1931
'matchGroupToUse',
@@ -57,9 +69,8 @@ class DirectAPKLink extends AppSource {
5769
additionalSettingsNew[s] = additionalSettings[s];
5870
}
5971
}
60-
additionalSettingsNew['defaultPseudoVersioningMethod'] = 'partialAPKHash';
6172
additionalSettingsNew['directAPKLink'] = true;
62-
additionalSettings['versionDetection'] = false;
73+
additionalSettingsNew['versionDetection'] = false;
6374
return html.getLatestAPKDetails(standardUrl, additionalSettingsNew);
6475
}
6576
}

lib/app_sources/html.dart

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ class HTML extends AppSource {
263263
'defaultPseudoVersioningMethod',
264264
[
265265
MapEntry('partialAPKHash', tr('partialAPKHash')),
266-
MapEntry('APKLinkHash', tr('APKLinkHash'))
266+
MapEntry('APKLinkHash', tr('APKLinkHash')),
267+
MapEntry('ETag', 'ETag')
267268
],
268269
label: tr('defaultPseudoVersioningMethod'),
269270
defaultValue: 'partialAPKHash')
@@ -356,14 +357,24 @@ class HTML extends AppSource {
356357
additionalSettings['versionExtractWholePage'] == true
357358
? versionExtractionWholePageString
358359
: relDecoded);
359-
version ??= additionalSettings['defaultPseudoVersioningMethod'] ==
360-
'APKLinkHash'
361-
? rel.hashCode.toString()
362-
: (await checkPartialDownloadHashDynamic(rel,
363-
headers: await getRequestHeaders(additionalSettings,
364-
forAPKDownload: true),
365-
allowInsecure: additionalSettings['allowInsecure'] == true))
366-
.toString();
360+
var apkReqHeaders =
361+
await getRequestHeaders(additionalSettings, forAPKDownload: true);
362+
if (version == null &&
363+
additionalSettings['defaultPseudoVersioningMethod'] == 'ETag') {
364+
version = await checkETagHeader(rel,
365+
headers: apkReqHeaders,
366+
allowInsecure: additionalSettings['allowInsecure'] == true);
367+
if (version == null) {
368+
throw NoVersionError();
369+
}
370+
}
371+
version ??=
372+
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
373+
? rel.hashCode.toString()
374+
: (await checkPartialDownloadHashDynamic(rel,
375+
headers: apkReqHeaders,
376+
allowInsecure: additionalSettings['allowInsecure'] == true))
377+
.toString();
367378
return APKDetails(
368379
version,
369380
[rel].map((e) {

lib/main.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ List<MapEntry<Locale, String>> supportedLocales = const [
4646
'Esperanto'), // https://github.com/aissat/easy_localization/issues/220#issuecomment-846035493
4747
MapEntry(Locale('in'), 'Bahasa Indonesia'),
4848
MapEntry(Locale('ko'), '한국어'),
49+
MapEntry(Locale('ca'), 'Català'),
4950
];
5051
const fallbackLocale = Locale('en');
5152
const localeDir = 'assets/translations';

lib/pages/add_app.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -575,8 +575,10 @@ class AddAppPageState extends State<AddAppPage> {
575575

576576
Widget getSourcesListWidget() => Padding(
577577
padding: const EdgeInsets.all(16),
578-
child: Row(
579-
mainAxisAlignment: MainAxisAlignment.spaceBetween,
578+
child: Wrap(
579+
direction: Axis.horizontal,
580+
alignment: WrapAlignment.spaceBetween,
581+
spacing: 12,
580582
children: [
581583
GestureDetector(
582584
onTap: () {

lib/providers/apps_provider.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,22 @@ Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
220220
return hashListOfLists(bytes);
221221
}
222222

223+
Future<String?> checkETagHeader(String url,
224+
{Map<String, String>? headers, bool allowInsecure = false}) async {
225+
// Send the initial request but cancel it as soon as you have the headers
226+
var reqHeaders = headers ?? {};
227+
var req = Request('GET', Uri.parse(url));
228+
req.headers.addAll(reqHeaders);
229+
var client = IOClient(createHttpClient(allowInsecure));
230+
StreamedResponse response = await client.send(req);
231+
var resHeaders = response.headers;
232+
client.close();
233+
return resHeaders[HttpHeaders.etagHeader]
234+
?.replaceAll('"', '')
235+
.hashCode
236+
.toString();
237+
}
238+
223239
Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
224240
Function? onProgress, String destDir,
225241
{bool useExisting = true,

lib/providers/source_provider.dart

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33

44
import 'dart:convert';
55
import 'dart:io';
6+
import 'package:http/http.dart' as http;
7+
import 'dart:typed_data';
68

79
import 'package:device_info_plus/device_info_plus.dart';
810
import 'package:easy_localization/easy_localization.dart';
911
import 'package:html/dom.dart';
1012
import 'package:http/http.dart';
11-
import 'package:http/io_client.dart';
1213
import 'package:obtainium/app_sources/apkmirror.dart';
1314
import 'package:obtainium/app_sources/apkpure.dart';
1415
import 'package:obtainium/app_sources/aptoide.dart';
@@ -566,23 +567,62 @@ abstract class AppSource {
566567
String url, Map<String, dynamic> additionalSettings,
567568
{bool followRedirects = true, Object? postBody}) async {
568569
var requestHeaders = await getRequestHeaders(additionalSettings);
570+
569571
if (requestHeaders != null || followRedirects == false) {
570-
var req = Request(postBody == null ? 'GET' : 'POST', Uri.parse(url));
571-
req.followRedirects = followRedirects;
572-
if (requestHeaders != null) {
573-
req.headers.addAll(requestHeaders);
574-
}
575-
if (postBody != null) {
576-
req.headers[HttpHeaders.contentTypeHeader] = 'application/json';
577-
req.body = jsonEncode(postBody);
572+
var method = postBody == null ? 'GET' : 'POST';
573+
var currentUrl = url;
574+
var redirectCount = 0;
575+
const maxRedirects = 10;
576+
while (redirectCount < maxRedirects) {
577+
var httpClient =
578+
createHttpClient(additionalSettings['allowInsecure'] == true);
579+
var request = await httpClient.openUrl(method, Uri.parse(currentUrl));
580+
if (requestHeaders != null) {
581+
requestHeaders.forEach((key, value) {
582+
request.headers.set(key, value);
583+
});
584+
}
585+
request.followRedirects = false;
586+
if (postBody != null) {
587+
request.headers.contentType = ContentType.json;
588+
request.write(jsonEncode(postBody));
589+
}
590+
final response = await request.close();
591+
592+
if (followRedirects &&
593+
(response.statusCode == 301 || response.statusCode == 302)) {
594+
final location = response.headers.value(HttpHeaders.locationHeader);
595+
if (location != null) {
596+
currentUrl = location;
597+
redirectCount++;
598+
httpClient.close();
599+
continue;
600+
}
601+
}
602+
603+
final bytes = (await response.fold<BytesBuilder>(
604+
BytesBuilder(), (b, d) => b..add(d)))
605+
.toBytes();
606+
607+
final headers = <String, String>{};
608+
response.headers.forEach((name, values) {
609+
headers[name] = values.join(', ');
610+
});
611+
612+
httpClient.close();
613+
614+
return http.Response.bytes(
615+
bytes,
616+
response.statusCode,
617+
headers: headers,
618+
request: http.Request(method, Uri.parse(url)),
619+
);
578620
}
579-
return Response.fromStream(await IOClient(
580-
createHttpClient(additionalSettings['allowInsecure'] == true))
581-
.send(req));
621+
throw ObtainiumError('Too many redirects ($maxRedirects)');
582622
} else {
583623
return postBody == null
584-
? get(Uri.parse(url))
585-
: post(Uri.parse(url), body: jsonEncode(postBody));
624+
? http.get(Uri.parse(url))
625+
: http.post(Uri.parse(url), body: jsonEncode(postBody));
586626
}
587627
}
588628

pubspec.lock

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -304,10 +304,10 @@ packages:
304304
dependency: "direct main"
305305
description:
306306
name: file_picker
307-
sha256: "09b474c0c8117484b80cbebc043801ff91e05cfbd2874d512825c899e1754694"
307+
sha256: "36a1652d99cb6bf8ccc8b9f43aded1fd60b234d23ce78af422c07f950a436ef7"
308308
url: "https://pub.dev"
309309
source: hosted
310-
version: "9.2.3"
310+
version: "10.0.0"
311311
fixnum:
312312
dependency: transitive
313313
description:
@@ -490,10 +490,10 @@ packages:
490490
dependency: "direct main"
491491
description:
492492
name: flutter_markdown
493-
sha256: e7bbc718adc9476aa14cfddc1ef048d2e21e4e8f18311aaac723266db9f9e7b5
493+
sha256: "634622a3a826d67cb05c0e3e576d1812c430fa98404e95b60b131775c73d76ec"
494494
url: "https://pub.dev"
495495
source: hosted
496-
version: "0.7.6+2"
496+
version: "0.7.7"
497497
flutter_plugin_android_lifecycle:
498498
dependency: transitive
499499
description:
@@ -1107,10 +1107,10 @@ packages:
11071107
dependency: transitive
11081108
description:
11091109
name: url_launcher_ios
1110-
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
1110+
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
11111111
url: "https://pub.dev"
11121112
source: hosted
1113-
version: "6.3.2"
1113+
version: "6.3.3"
11141114
url_launcher_linux:
11151115
dependency: transitive
11161116
description:
@@ -1211,10 +1211,10 @@ packages:
12111211
dependency: transitive
12121212
description:
12131213
name: webview_flutter_wkwebview
1214-
sha256: c49a98510080378b1525132f407a92c3dcd3b7145bef04fb8137724aadcf1cf0
1214+
sha256: c14455137ce60a68e1ccaf4e8f2dae8cebcb3465ddaa2fcfb57584fb7c5afe4d
12151215
url: "https://pub.dev"
12161216
source: hosted
1217-
version: "3.18.4"
1217+
version: "3.18.5"
12181218
win32:
12191219
dependency: transitive
12201220
description:

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
1616
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
1717
# In Windows, build-name is used as the major, minor, and patch parts
1818
# of the product and file versions while build-number is used as the build suffix.
19-
version: 1.1.48+2305
19+
version: 1.1.49+2306
2020

2121
environment:
2222
sdk: ^3.6.0
@@ -47,7 +47,7 @@ dependencies:
4747
permission_handler: ^11.0.0
4848
fluttertoast: ^8.0.9
4949
device_info_plus: ^11.0.0
50-
file_picker: ^9.0.0
50+
file_picker: ^10.0.0
5151
animations: ^2.0.4
5252
android_package_installer: # TODO: See if PR will be accepted (dev may not be active), else remove this comment
5353
git:

0 commit comments

Comments
 (0)