mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 13:26:43 +02:00
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)
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:obtainium/app_sources/html.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
@ -8,12 +9,23 @@ class DirectAPKLink extends AppSource {
|
||||
|
||||
DirectAPKLink() {
|
||||
name = tr('directAPKLink');
|
||||
additionalSourceAppSpecificSettingFormItems = html
|
||||
.additionalSourceAppSpecificSettingFormItems
|
||||
additionalSourceAppSpecificSettingFormItems = [
|
||||
...html.additionalSourceAppSpecificSettingFormItems
|
||||
.where((element) => element
|
||||
.where((element) => element.key == 'requestHeader')
|
||||
.isNotEmpty)
|
||||
.toList();
|
||||
.toList(),
|
||||
[
|
||||
GeneratedFormDropdown(
|
||||
'defaultPseudoVersioningMethod',
|
||||
[
|
||||
MapEntry('partialAPKHash', tr('partialAPKHash')),
|
||||
MapEntry('ETag', 'ETag')
|
||||
],
|
||||
label: tr('defaultPseudoVersioningMethod'),
|
||||
defaultValue: 'partialAPKHash')
|
||||
]
|
||||
];
|
||||
excludeCommonSettingKeys = [
|
||||
'versionExtractionRegEx',
|
||||
'matchGroupToUse',
|
||||
@ -57,9 +69,8 @@ class DirectAPKLink extends AppSource {
|
||||
additionalSettingsNew[s] = additionalSettings[s];
|
||||
}
|
||||
}
|
||||
additionalSettingsNew['defaultPseudoVersioningMethod'] = 'partialAPKHash';
|
||||
additionalSettingsNew['directAPKLink'] = true;
|
||||
additionalSettings['versionDetection'] = false;
|
||||
additionalSettingsNew['versionDetection'] = false;
|
||||
return html.getLatestAPKDetails(standardUrl, additionalSettingsNew);
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +263,8 @@ class HTML extends AppSource {
|
||||
'defaultPseudoVersioningMethod',
|
||||
[
|
||||
MapEntry('partialAPKHash', tr('partialAPKHash')),
|
||||
MapEntry('APKLinkHash', tr('APKLinkHash'))
|
||||
MapEntry('APKLinkHash', tr('APKLinkHash')),
|
||||
MapEntry('ETag', 'ETag')
|
||||
],
|
||||
label: tr('defaultPseudoVersioningMethod'),
|
||||
defaultValue: 'partialAPKHash')
|
||||
@ -356,12 +357,22 @@ class HTML extends AppSource {
|
||||
additionalSettings['versionExtractWholePage'] == true
|
||||
? versionExtractionWholePageString
|
||||
: relDecoded);
|
||||
version ??= additionalSettings['defaultPseudoVersioningMethod'] ==
|
||||
'APKLinkHash'
|
||||
var apkReqHeaders =
|
||||
await getRequestHeaders(additionalSettings, forAPKDownload: true);
|
||||
if (version == null &&
|
||||
additionalSettings['defaultPseudoVersioningMethod'] == 'ETag') {
|
||||
version = await checkETagHeader(rel,
|
||||
headers: apkReqHeaders,
|
||||
allowInsecure: additionalSettings['allowInsecure'] == true);
|
||||
if (version == null) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
}
|
||||
version ??=
|
||||
additionalSettings['defaultPseudoVersioningMethod'] == 'APKLinkHash'
|
||||
? rel.hashCode.toString()
|
||||
: (await checkPartialDownloadHashDynamic(rel,
|
||||
headers: await getRequestHeaders(additionalSettings,
|
||||
forAPKDownload: true),
|
||||
headers: apkReqHeaders,
|
||||
allowInsecure: additionalSettings['allowInsecure'] == true))
|
||||
.toString();
|
||||
return APKDetails(
|
||||
|
@ -46,6 +46,7 @@ List<MapEntry<Locale, String>> supportedLocales = const [
|
||||
'Esperanto'), // https://github.com/aissat/easy_localization/issues/220#issuecomment-846035493
|
||||
MapEntry(Locale('in'), 'Bahasa Indonesia'),
|
||||
MapEntry(Locale('ko'), '한국어'),
|
||||
MapEntry(Locale('ca'), 'Català'),
|
||||
];
|
||||
const fallbackLocale = Locale('en');
|
||||
const localeDir = 'assets/translations';
|
||||
|
@ -575,8 +575,10 @@ class AddAppPageState extends State<AddAppPage> {
|
||||
|
||||
Widget getSourcesListWidget() => Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
child: Wrap(
|
||||
direction: Axis.horizontal,
|
||||
alignment: WrapAlignment.spaceBetween,
|
||||
spacing: 12,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
|
@ -220,6 +220,22 @@ Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
|
||||
return hashListOfLists(bytes);
|
||||
}
|
||||
|
||||
Future<String?> checkETagHeader(String url,
|
||||
{Map<String, String>? headers, bool allowInsecure = false}) async {
|
||||
// Send the initial request but cancel it as soon as you have the headers
|
||||
var reqHeaders = headers ?? {};
|
||||
var req = Request('GET', Uri.parse(url));
|
||||
req.headers.addAll(reqHeaders);
|
||||
var client = IOClient(createHttpClient(allowInsecure));
|
||||
StreamedResponse response = await client.send(req);
|
||||
var resHeaders = response.headers;
|
||||
client.close();
|
||||
return resHeaders[HttpHeaders.etagHeader]
|
||||
?.replaceAll('"', '')
|
||||
.hashCode
|
||||
.toString();
|
||||
}
|
||||
|
||||
Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
|
||||
Function? onProgress, String destDir,
|
||||
{bool useExisting = true,
|
||||
|
@ -3,12 +3,13 @@
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:obtainium/app_sources/apkmirror.dart';
|
||||
import 'package:obtainium/app_sources/apkpure.dart';
|
||||
import 'package:obtainium/app_sources/aptoide.dart';
|
||||
@ -566,23 +567,62 @@ abstract class AppSource {
|
||||
String url, Map<String, dynamic> additionalSettings,
|
||||
{bool followRedirects = true, Object? postBody}) async {
|
||||
var requestHeaders = await getRequestHeaders(additionalSettings);
|
||||
|
||||
if (requestHeaders != null || followRedirects == false) {
|
||||
var req = Request(postBody == null ? 'GET' : 'POST', Uri.parse(url));
|
||||
req.followRedirects = followRedirects;
|
||||
var method = postBody == null ? 'GET' : 'POST';
|
||||
var currentUrl = url;
|
||||
var redirectCount = 0;
|
||||
const maxRedirects = 10;
|
||||
while (redirectCount < maxRedirects) {
|
||||
var httpClient =
|
||||
createHttpClient(additionalSettings['allowInsecure'] == true);
|
||||
var request = await httpClient.openUrl(method, Uri.parse(currentUrl));
|
||||
if (requestHeaders != null) {
|
||||
req.headers.addAll(requestHeaders);
|
||||
requestHeaders.forEach((key, value) {
|
||||
request.headers.set(key, value);
|
||||
});
|
||||
}
|
||||
request.followRedirects = false;
|
||||
if (postBody != null) {
|
||||
req.headers[HttpHeaders.contentTypeHeader] = 'application/json';
|
||||
req.body = jsonEncode(postBody);
|
||||
request.headers.contentType = ContentType.json;
|
||||
request.write(jsonEncode(postBody));
|
||||
}
|
||||
return Response.fromStream(await IOClient(
|
||||
createHttpClient(additionalSettings['allowInsecure'] == true))
|
||||
.send(req));
|
||||
final response = await request.close();
|
||||
|
||||
if (followRedirects &&
|
||||
(response.statusCode == 301 || response.statusCode == 302)) {
|
||||
final location = response.headers.value(HttpHeaders.locationHeader);
|
||||
if (location != null) {
|
||||
currentUrl = location;
|
||||
redirectCount++;
|
||||
httpClient.close();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
final bytes = (await response.fold<BytesBuilder>(
|
||||
BytesBuilder(), (b, d) => b..add(d)))
|
||||
.toBytes();
|
||||
|
||||
final headers = <String, String>{};
|
||||
response.headers.forEach((name, values) {
|
||||
headers[name] = values.join(', ');
|
||||
});
|
||||
|
||||
httpClient.close();
|
||||
|
||||
return http.Response.bytes(
|
||||
bytes,
|
||||
response.statusCode,
|
||||
headers: headers,
|
||||
request: http.Request(method, Uri.parse(url)),
|
||||
);
|
||||
}
|
||||
throw ObtainiumError('Too many redirects ($maxRedirects)');
|
||||
} else {
|
||||
return postBody == null
|
||||
? get(Uri.parse(url))
|
||||
: post(Uri.parse(url), body: jsonEncode(postBody));
|
||||
? http.get(Uri.parse(url))
|
||||
: http.post(Uri.parse(url), body: jsonEncode(postBody));
|
||||
}
|
||||
}
|
||||
|
||||
|
16
pubspec.lock
16
pubspec.lock
@ -304,10 +304,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "09b474c0c8117484b80cbebc043801ff91e05cfbd2874d512825c899e1754694"
|
||||
sha256: "36a1652d99cb6bf8ccc8b9f43aded1fd60b234d23ce78af422c07f950a436ef7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.3"
|
||||
version: "10.0.0"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -490,10 +490,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: e7bbc718adc9476aa14cfddc1ef048d2e21e4e8f18311aaac723266db9f9e7b5
|
||||
sha256: "634622a3a826d67cb05c0e3e576d1812c430fa98404e95b60b131775c73d76ec"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.6+2"
|
||||
version: "0.7.7"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1107,10 +1107,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
|
||||
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
version: "6.3.3"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1211,10 +1211,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: c49a98510080378b1525132f407a92c3dcd3b7145bef04fb8137724aadcf1cf0
|
||||
sha256: c14455137ce60a68e1ccaf4e8f2dae8cebcb3465ddaa2fcfb57584fb7c5afe4d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.18.4"
|
||||
version: "3.18.5"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 1.1.48+2305
|
||||
version: 1.1.49+2306
|
||||
|
||||
environment:
|
||||
sdk: ^3.6.0
|
||||
@ -47,7 +47,7 @@ dependencies:
|
||||
permission_handler: ^11.0.0
|
||||
fluttertoast: ^8.0.9
|
||||
device_info_plus: ^11.0.0
|
||||
file_picker: ^9.0.0
|
||||
file_picker: ^10.0.0
|
||||
animations: ^2.0.4
|
||||
android_package_installer: # TODO: See if PR will be accepted (dev may not be active), else remove this comment
|
||||
git:
|
||||
|
Reference in New Issue
Block a user