mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-21 02:01:03 +07:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 787089f4ae | |||
| ebffda68c2 | |||
| 8ce5237a5f | |||
| 129ae44edc | |||
| 16fc44f989 | |||
| ef909d3605 | |||
| b9ca3315c6 | |||
| e9ed5b59a4 | |||
| 047dbb2677 | |||
| e9efe32f9b | |||
| 2dd3531e78 | |||
| 129f79ca2c | |||
| 50769f231d |
@@ -547,7 +547,7 @@ jobs:
|
|||||||
|
|
||||||
env:
|
env:
|
||||||
ANDROID_BUILD_PLATFORM: android-36
|
ANDROID_BUILD_PLATFORM: android-36
|
||||||
QT_VERSION: 6.10.1
|
QT_VERSION: 6.10.3
|
||||||
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
QT_MODULES: 'qtremoteobjects qt5compat qtimageformats qtshadertools'
|
||||||
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
PROD_AGW_PUBLIC_KEY: ${{ secrets.PROD_AGW_PUBLIC_KEY }}
|
||||||
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
PROD_S3_ENDPOINT: ${{ secrets.PROD_S3_ENDPOINT }}
|
||||||
|
|||||||
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
set(AMNEZIAVPN_VERSION 4.8.15.4)
|
set(AMNEZIAVPN_VERSION 4.8.19.0)
|
||||||
|
|
||||||
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 2120)
|
set(APP_ANDROID_VERSION_CODE 2129)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
+1
-1
Submodule client/3rd-prebuilt updated: 51bb4703a4...4680bd8fb4
@@ -42,6 +42,7 @@ import androidx.core.view.OnApplyWindowInsetsListener
|
|||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
@@ -766,7 +767,13 @@ class AmneziaActivity : QtActivity() {
|
|||||||
fun openFile(filter: String?) {
|
fun openFile(filter: String?) {
|
||||||
Log.v(TAG, "Open file with filter: $filter")
|
Log.v(TAG, "Open file with filter: $filter")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
val intent = if (!isOnTv()) {
|
val systemPickerPackage = listOf("com.google.android.documentsui", "com.android.documentsui")
|
||||||
|
.firstOrNull { pkg ->
|
||||||
|
try { packageManager.getPackageInfo(pkg, 0); true }
|
||||||
|
catch (_: PackageManager.NameNotFoundException) { false }
|
||||||
|
}
|
||||||
|
|
||||||
|
val intent = if (!isOnTv() && systemPickerPackage != null) {
|
||||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||||
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||||
val mime = MimeTypeMap.getSingleton()
|
val mime = MimeTypeMap.getSingleton()
|
||||||
@@ -792,6 +799,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
else -> type = "*/*"
|
else -> type = "*/*"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
`package` = systemPickerPackage
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Intent(this@AmneziaActivity, TvFilePicker::class.java)
|
Intent(this@AmneziaActivity, TvFilePicker::class.java)
|
||||||
@@ -803,8 +811,11 @@ class AmneziaActivity : QtActivity() {
|
|||||||
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
|
if (isOnTv() && it?.hasExtra("activityNotFound") == true) {
|
||||||
showNoFileBrowserAlertDialog()
|
showNoFileBrowserAlertDialog()
|
||||||
}
|
}
|
||||||
val uri = it?.data?.apply {
|
val uri = it?.data?.let { u ->
|
||||||
grantUriPermission(packageName, this, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
if (u.scheme == "content") {
|
||||||
|
try { grantUriPermission(packageName, u, Intent.FLAG_GRANT_READ_URI_PERMISSION) } catch (_: Exception) {}
|
||||||
|
}
|
||||||
|
u
|
||||||
}?.toString() ?: ""
|
}?.toString() ?: ""
|
||||||
Log.v(TAG, "Open file: $uri")
|
Log.v(TAG, "Open file: $uri")
|
||||||
if (uri.isNotEmpty()) {
|
if (uri.isNotEmpty()) {
|
||||||
@@ -844,7 +855,12 @@ class AmneziaActivity : QtActivity() {
|
|||||||
Log.v(TAG, "Get fd for $fileName")
|
Log.v(TAG, "Get fd for $fileName")
|
||||||
return blockingCall(Dispatchers.IO) {
|
return blockingCall(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
pfd = contentResolver.openFileDescriptor(Uri.parse(fileName), "r")
|
val uri = Uri.parse(fileName)
|
||||||
|
pfd = if (uri.scheme == "file") {
|
||||||
|
ParcelFileDescriptor.open(File(uri.path!!), ParcelFileDescriptor.MODE_READ_ONLY)
|
||||||
|
} else {
|
||||||
|
contentResolver.openFileDescriptor(uri, "r")
|
||||||
|
}
|
||||||
pfd?.fd ?: -1
|
pfd?.fd ?: -1
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to get fd: $e")
|
Log.e(TAG, "Failed to get fd: $e")
|
||||||
@@ -1064,13 +1080,11 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun sendTouch(x: Float, y: Float) {
|
fun sendTouch(x: Float, y: Float) {
|
||||||
Log.v(TAG, "Send touch: $x, $y")
|
Log.v(TAG, "Send touch: $x, $y")
|
||||||
blockingCall {
|
|
||||||
findQtWindow(window.decorView)?.let {
|
findQtWindow(window.decorView)?.let {
|
||||||
Log.v(TAG, "Send touch to $it")
|
Log.v(TAG, "Send touch to $it")
|
||||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
|
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
|
||||||
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
|
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findQtWindow(view: View): View? {
|
private fun findQtWindow(view: View): View? {
|
||||||
|
|||||||
@@ -1,30 +1,36 @@
|
|||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.content.ActivityNotFoundException
|
import android.content.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.os.Environment
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
private const val TAG = "TvFilePicker"
|
private const val TAG = "TvFilePicker"
|
||||||
|
private const val READ_STORAGE_REQUEST_CODE = 1001
|
||||||
|
|
||||||
class TvFilePicker : ComponentActivity() {
|
class TvFilePicker : ComponentActivity() {
|
||||||
|
|
||||||
private val fileChooseResultLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
// SAF launcher for Android 10+ where File API is blocked by scoped storage
|
||||||
|
private val safLauncher = registerForActivityResult(object : ActivityResultContracts.OpenDocument() {
|
||||||
override fun createIntent(context: Context, input: Array<String>): Intent {
|
override fun createIntent(context: Context, input: Array<String>): Intent {
|
||||||
val intent = super.createIntent(context, input)
|
val intent = super.createIntent(context, input)
|
||||||
|
val activities = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
val activitiesToResolveIntent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
context.packageManager.queryIntentActivities(intent, PackageManager.ResolveInfoFlags.of(PackageManager.MATCH_DEFAULT_ONLY.toLong()))
|
||||||
} else {
|
} else {
|
||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
context.packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
|
||||||
}
|
}
|
||||||
if (activitiesToResolveIntent.all {
|
if (activities.all {
|
||||||
val name = it.activityInfo.packageName
|
val name = it.activityInfo.packageName
|
||||||
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
|
name.startsWith("com.google.android.tv.frameworkpackagestubs") || name.startsWith("com.android.tv.frameworkpackagestubs")
|
||||||
}) {
|
}) {
|
||||||
@@ -32,38 +38,140 @@ class TvFilePicker : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
return intent
|
return intent
|
||||||
}
|
}
|
||||||
}) {
|
}) { uri ->
|
||||||
setResult(RESULT_OK, Intent().apply {
|
setResult(RESULT_OK, Intent().apply {
|
||||||
data = it
|
data = uri
|
||||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
})
|
})
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val directoryStack = ArrayDeque<File>()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.v(TAG, "onCreate")
|
Log.v(TAG, "onCreate")
|
||||||
getFile()
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
launchSaf()
|
||||||
|
} else {
|
||||||
|
checkPermissionAndBrowse()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
@Deprecated("Deprecated in Java")
|
||||||
super.onNewIntent(intent)
|
override fun onBackPressed() {
|
||||||
Log.v(TAG, "onNewIntent")
|
navigateBack()
|
||||||
getFile()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getFile() {
|
private fun launchSaf() {
|
||||||
try {
|
try {
|
||||||
Log.v(TAG, "getFile")
|
safLauncher.launch(arrayOf("*/*"))
|
||||||
fileChooseResultLauncher.launch(arrayOf("*/*"))
|
|
||||||
} catch (_: ActivityNotFoundException) {
|
} catch (_: ActivityNotFoundException) {
|
||||||
Log.w(TAG, "Activity not found")
|
Log.w(TAG, "No SAF activity found")
|
||||||
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
setResult(RESULT_CANCELED, Intent().apply { putExtra("activityNotFound", true) })
|
||||||
finish()
|
finish()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "Failed to get file: $e")
|
Log.e(TAG, "SAF launch failed: $e")
|
||||||
setResult(RESULT_CANCELED)
|
setResult(RESULT_CANCELED)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun checkPermissionAndBrowse() {
|
||||||
|
if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
requestPermissions(arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), READ_STORAGE_REQUEST_CODE)
|
||||||
|
} else {
|
||||||
|
showRootDirectory()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == READ_STORAGE_REQUEST_CODE &&
|
||||||
|
grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
showRootDirectory()
|
||||||
|
} else {
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showRootDirectory() {
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
val primaryExternal = Environment.getExternalStorageDirectory()
|
||||||
|
val storageDir = File("/storage")
|
||||||
|
// Pre-seed stack with /storage so Back from primary storage goes there (USB drives etc.)
|
||||||
|
if (storageDir.exists() && storageDir.canonicalPath != primaryExternal.canonicalPath) {
|
||||||
|
directoryStack.addLast(storageDir)
|
||||||
|
}
|
||||||
|
showDirectory(primaryExternal)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigateBack() {
|
||||||
|
if (directoryStack.size > 1) {
|
||||||
|
directoryStack.removeLast()
|
||||||
|
val parent = directoryStack.removeLast()
|
||||||
|
showDirectory(parent)
|
||||||
|
} else {
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDirectory(dir: File) {
|
||||||
|
directoryStack.addLast(dir)
|
||||||
|
Log.v(TAG, "Showing directory: ${dir.absolutePath}")
|
||||||
|
|
||||||
|
val entries = try {
|
||||||
|
dir.listFiles()
|
||||||
|
?.sortedWith(compareBy({ !it.isDirectory }, { it.name.lowercase() }))
|
||||||
|
?: emptyList()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Failed to list directory: $e")
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val names = entries.map { if (it.isDirectory) "[${it.name}]" else it.name }.toTypedArray()
|
||||||
|
|
||||||
|
val builder = AlertDialog.Builder(this)
|
||||||
|
.setTitle(dir.absolutePath)
|
||||||
|
|
||||||
|
if (entries.isEmpty()) {
|
||||||
|
builder.setMessage("No files available")
|
||||||
|
} else {
|
||||||
|
builder.setItems(names) { dialog, which ->
|
||||||
|
dialog.dismiss()
|
||||||
|
val selected = entries[which]
|
||||||
|
if (selected.isDirectory) {
|
||||||
|
showDirectory(selected)
|
||||||
|
} else {
|
||||||
|
Log.v(TAG, "Selected file: ${selected.absolutePath}")
|
||||||
|
setResult(RESULT_OK, Intent().apply {
|
||||||
|
data = Uri.fromFile(selected)
|
||||||
|
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
})
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (directoryStack.size > 1) {
|
||||||
|
builder.setNegativeButton("↑ Back") { dialog, _ ->
|
||||||
|
dialog.dismiss()
|
||||||
|
navigateBack()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.setOnCancelListener {
|
||||||
|
setResult(RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "amnezia_application.h"
|
#include "amnezia_application.h"
|
||||||
#include "core/api/apiUtils.h"
|
#include "core/api/apiUtils.h"
|
||||||
#include "core/networkUtilities.h"
|
#include "core/networkUtilities.h"
|
||||||
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
#ifdef AMNEZIA_DESKTOP
|
#ifdef AMNEZIA_DESKTOP
|
||||||
@@ -51,15 +52,78 @@ namespace
|
|||||||
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
constexpr QLatin1String unprocessableSubscriptionMessage("Failed to retrieve subscription information. Is it activated?");
|
||||||
|
|
||||||
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
|
constexpr int proxyStorageRequestTimeoutMsecs = 3000;
|
||||||
|
|
||||||
|
QStringList shuffledProxyUrls(const QStringList &proxyUrls)
|
||||||
|
{
|
||||||
|
QStringList shuffled = proxyUrls;
|
||||||
|
std::random_device randomDevice;
|
||||||
|
std::mt19937 generator(randomDevice());
|
||||||
|
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
||||||
|
return shuffled;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getProxyUrlsCacheKey(const QString &serviceType, const QString &userCountryCode)
|
||||||
|
{
|
||||||
|
return QStringLiteral("service_%1_country_%2").arg(serviceType, userCountryCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool decryptProxyUrlsPayload(const QByteArray &encryptedPayload, bool isDevEnvironment, QByteArray &decryptedPayload)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
QByteArray key = isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
||||||
|
if (!isDevEnvironment) {
|
||||||
|
QCryptographicHash hash(QCryptographicHash::Sha512);
|
||||||
|
hash.addData(key);
|
||||||
|
QByteArray h = hash.result().toHex();
|
||||||
|
|
||||||
|
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
||||||
|
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
||||||
|
QByteArray ba = QByteArray::fromBase64(encryptedPayload);
|
||||||
|
|
||||||
|
QSimpleCrypto::QBlockCipher cipher;
|
||||||
|
decryptedPayload = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
||||||
|
} else {
|
||||||
|
decryptedPayload = encryptedPayload;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (...) {
|
||||||
|
Utils::logException();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList readCachedProxyUrls(const QByteArray &cachedProxyUrlsEncrypted, bool isDevEnvironment)
|
||||||
|
{
|
||||||
|
if (cachedProxyUrlsEncrypted.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray cachedProxyUrlsDecrypted;
|
||||||
|
if (!decryptProxyUrlsPayload(cachedProxyUrlsEncrypted, isDevEnvironment, cachedProxyUrlsDecrypted)) {
|
||||||
|
qCritical() << "error decrypting cached proxy urls payload";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray endpointsArray = QJsonDocument::fromJson(cachedProxyUrlsDecrypted).array();
|
||||||
|
QStringList endpoints;
|
||||||
|
endpoints.reserve(endpointsArray.size());
|
||||||
|
for (const QJsonValue &endpoint : endpointsArray) {
|
||||||
|
endpoints.push_back(endpoint.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return endpoints;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
GatewayController::GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
const bool isStrictKillSwitchEnabled, QObject *parent)
|
const bool isStrictKillSwitchEnabled, const std::shared_ptr<Settings> &settings,
|
||||||
|
QObject *parent)
|
||||||
: QObject(parent),
|
: QObject(parent),
|
||||||
m_gatewayEndpoint(gatewayEndpoint),
|
m_gatewayEndpoint(gatewayEndpoint),
|
||||||
m_isDevEnvironment(isDevEnvironment),
|
m_isDevEnvironment(isDevEnvironment),
|
||||||
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
m_requestTimeoutMsecs(requestTimeoutMsecs),
|
||||||
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled)
|
m_isStrictKillSwitchEnabled(isStrictKillSwitchEnabled),
|
||||||
|
m_settings(settings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,8 +374,9 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
|
|||||||
QStringList proxyStorageUrls;
|
QStringList proxyStorageUrls;
|
||||||
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||||
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||||
|
const QString proxyUrlsCacheKey = getProxyUrlsCacheKey(serviceType, userCountryCode);
|
||||||
|
|
||||||
getProxyUrlsAsync(proxyStorageUrls, 0, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
getProxyUrlsAsync(proxyStorageUrls, 0, proxyUrlsCacheKey, [this, encRequestData, endpoint, processResponse](const QStringList &proxyUrls) {
|
||||||
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
|
getProxyUrlAsync(proxyUrls, 0, [this, encRequestData, endpoint, processResponse](const QString &proxyUrl) {
|
||||||
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
|
bypassProxyAsync(endpoint, proxyUrl, encRequestData,
|
||||||
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
|
[processResponse, this](const QByteArray &decryptedBody, bool isDecryptionSuccessful,
|
||||||
@@ -357,8 +422,6 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
std::shuffle(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
|
std::shuffle(primaryBaseUrls.begin(), primaryBaseUrls.end(), generator);
|
||||||
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.end(), generator);
|
std::shuffle(fallbackBaseUrls.begin(), fallbackBaseUrls.end(), generator);
|
||||||
|
|
||||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
|
||||||
|
|
||||||
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
|
auto appendStorageUrls = [&serviceType, &userCountryCode](const QStringList &baseUrls, QStringList &target) {
|
||||||
if (!serviceType.isEmpty()) {
|
if (!serviceType.isEmpty()) {
|
||||||
for (const auto &baseUrl : baseUrls) {
|
for (const auto &baseUrl : baseUrls) {
|
||||||
@@ -374,10 +437,12 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
QStringList proxyStorageUrls;
|
QStringList proxyStorageUrls;
|
||||||
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
appendStorageUrls(primaryBaseUrls, proxyStorageUrls);
|
||||||
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
appendStorageUrls(fallbackBaseUrls, proxyStorageUrls);
|
||||||
|
const QString proxyUrlsCacheKey = getProxyUrlsCacheKey(serviceType, userCountryCode);
|
||||||
|
const QByteArray cachedProxyUrlsEncrypted = m_settings->readGatewayProxyUrls(proxyUrlsCacheKey);
|
||||||
|
|
||||||
if (proxyStorageUrls.empty()) {
|
if (proxyStorageUrls.empty()) {
|
||||||
qDebug() << "empty storage endpoint list";
|
qDebug() << "empty storage endpoint list";
|
||||||
return {};
|
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
for (const auto &proxyStorageUrl : proxyStorageUrls) {
|
||||||
@@ -392,26 +457,8 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
auto encryptedResponseBody = reply->readAll();
|
auto encryptedResponseBody = reply->readAll();
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
EVP_PKEY *privateKey = nullptr;
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
try {
|
if (!decryptProxyUrlsPayload(encryptedResponseBody, m_isDevEnvironment, responseBody)) {
|
||||||
if (!m_isDevEnvironment) {
|
|
||||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
|
||||||
hash.addData(key);
|
|
||||||
QByteArray hashResult = hash.result().toHex();
|
|
||||||
|
|
||||||
QByteArray key = QByteArray::fromHex(hashResult.left(64));
|
|
||||||
QByteArray iv = QByteArray::fromHex(hashResult.mid(64, 32));
|
|
||||||
|
|
||||||
QByteArray ba = QByteArray::fromBase64(encryptedResponseBody);
|
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher blockCipher;
|
|
||||||
responseBody = blockCipher.decryptAesBlockCipher(ba, key, iv);
|
|
||||||
} else {
|
|
||||||
responseBody = encryptedResponseBody;
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
Utils::logException();
|
|
||||||
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
qCritical() << "error loading private key from environment variables or decrypting payload" << encryptedResponseBody;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -422,6 +469,8 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
for (const auto &endpoint : endpointsArray) {
|
for (const auto &endpoint : endpointsArray) {
|
||||||
endpoints.push_back(endpoint.toString());
|
endpoints.push_back(endpoint.toString());
|
||||||
}
|
}
|
||||||
|
m_settings->writeGatewayProxyUrls(proxyUrlsCacheKey, encryptedResponseBody);
|
||||||
|
|
||||||
return endpoints;
|
return endpoints;
|
||||||
} else {
|
} else {
|
||||||
auto replyError = reply->error();
|
auto replyError = reply->error();
|
||||||
@@ -433,7 +482,7 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
|
|||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
|
bool GatewayController::shouldBypassProxy(const QNetworkReply::NetworkError &replyError, const QByteArray &decryptedResponseBody,
|
||||||
@@ -571,10 +620,12 @@ void GatewayController::bypassProxy(const QString &endpoint, const QString &serv
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||||
std::function<void(const QStringList &)> onComplete)
|
const QString &proxyUrlsCacheKey, std::function<void(const QStringList &)> onComplete)
|
||||||
{
|
{
|
||||||
|
const QByteArray cachedProxyUrlsEncrypted = m_settings->readGatewayProxyUrls(proxyUrlsCacheKey);
|
||||||
|
|
||||||
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
if (currentProxyStorageIndex >= proxyStorageUrls.size()) {
|
||||||
onComplete({});
|
onComplete(shuffledProxyUrls(readCachedProxyUrls(cachedProxyUrlsEncrypted, m_isDevEnvironment)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -587,33 +638,17 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
|||||||
|
|
||||||
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
|
// connect(reply, &QNetworkReply::sslErrors, this, [state](const QList<QSslError> &e) { *(state->sslErrors) = e; });
|
||||||
|
|
||||||
connect(reply, &QNetworkReply::finished, this, [this, proxyStorageUrls, currentProxyStorageIndex, onComplete, reply]() {
|
connect(reply, &QNetworkReply::finished, this,
|
||||||
|
[this, proxyStorageUrls, currentProxyStorageIndex, proxyUrlsCacheKey, onComplete, reply]() {
|
||||||
if (reply->error() == QNetworkReply::NoError) {
|
if (reply->error() == QNetworkReply::NoError) {
|
||||||
QByteArray encrypted = reply->readAll();
|
QByteArray encrypted = reply->readAll();
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
|
||||||
QByteArray responseBody;
|
QByteArray responseBody;
|
||||||
try {
|
if (!decryptProxyUrlsPayload(encrypted, m_isDevEnvironment, responseBody)) {
|
||||||
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
|
|
||||||
if (!m_isDevEnvironment) {
|
|
||||||
QCryptographicHash hash(QCryptographicHash::Sha512);
|
|
||||||
hash.addData(key);
|
|
||||||
QByteArray h = hash.result().toHex();
|
|
||||||
|
|
||||||
QByteArray decKey = QByteArray::fromHex(h.left(64));
|
|
||||||
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
|
|
||||||
QByteArray ba = QByteArray::fromBase64(encrypted);
|
|
||||||
|
|
||||||
QSimpleCrypto::QBlockCipher cipher;
|
|
||||||
responseBody = cipher.decryptAesBlockCipher(ba, decKey, iv);
|
|
||||||
} else {
|
|
||||||
responseBody = encrypted;
|
|
||||||
}
|
|
||||||
} catch (...) {
|
|
||||||
Utils::logException();
|
|
||||||
qCritical() << "error decrypting payload";
|
qCritical() << "error decrypting payload";
|
||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(
|
||||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,13 +656,9 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
|||||||
QStringList endpoints;
|
QStringList endpoints;
|
||||||
for (const QJsonValue &endpoint : endpointsArray)
|
for (const QJsonValue &endpoint : endpointsArray)
|
||||||
endpoints.push_back(endpoint.toString());
|
endpoints.push_back(endpoint.toString());
|
||||||
|
m_settings->writeGatewayProxyUrls(proxyUrlsCacheKey, encrypted);
|
||||||
|
|
||||||
QStringList shuffled = endpoints;
|
onComplete(shuffledProxyUrls(endpoints));
|
||||||
std::random_device randomDevice;
|
|
||||||
std::mt19937 generator(randomDevice());
|
|
||||||
std::shuffle(shuffled.begin(), shuffled.end(), generator);
|
|
||||||
|
|
||||||
onComplete(shuffled);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -636,7 +667,7 @@ void GatewayController::getProxyUrlsAsync(const QStringList proxyStorageUrls, co
|
|||||||
qDebug() << "go to the next storage endpoint";
|
qDebug() << "go to the next storage endpoint";
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
QMetaObject::invokeMethod(
|
QMetaObject::invokeMethod(
|
||||||
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, onComplete); }, Qt::QueuedConnection);
|
this, [=]() { getProxyUrlsAsync(proxyStorageUrls, currentProxyStorageIndex + 1, proxyUrlsCacheKey, onComplete); }, Qt::QueuedConnection);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,9 @@
|
|||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QPromise>
|
#include <QPromise>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
@@ -14,13 +17,16 @@
|
|||||||
#include "platforms/ios/ios_controller.h"
|
#include "platforms/ios/ios_controller.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
class Settings;
|
||||||
|
|
||||||
class GatewayController : public QObject
|
class GatewayController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
explicit GatewayController(const QString &gatewayEndpoint, const bool isDevEnvironment, const int requestTimeoutMsecs,
|
||||||
const bool isStrictKillSwitchEnabled, QObject *parent = nullptr);
|
const bool isStrictKillSwitchEnabled, const std::shared_ptr<Settings> &settings,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
amnezia::ErrorCode post(const QString &endpoint, const QJsonObject apiPayload, QByteArray &responseBody);
|
||||||
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
QFuture<QPair<amnezia::ErrorCode, QByteArray>> postAsync(const QString &endpoint, const QJsonObject apiPayload);
|
||||||
@@ -53,7 +59,7 @@ private:
|
|||||||
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
std::function<bool(QNetworkReply *reply, const QList<QSslError> &sslErrors)> replyProcessingFunction);
|
||||||
|
|
||||||
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
void getProxyUrlsAsync(const QStringList proxyStorageUrls, const int currentProxyStorageIndex,
|
||||||
std::function<void(const QStringList &)> onComplete);
|
const QString &proxyUrlsCacheKey, std::function<void(const QStringList &)> onComplete);
|
||||||
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
|
void getProxyUrlAsync(const QStringList proxyUrls, const int currentProxyIndex, std::function<void(const QString &)> onComplete);
|
||||||
void bypassProxyAsync(
|
void bypassProxyAsync(
|
||||||
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
const QString &endpoint, const QString &proxyUrl, EncryptedRequestData encRequestData,
|
||||||
@@ -63,6 +69,7 @@ private:
|
|||||||
QString m_gatewayEndpoint;
|
QString m_gatewayEndpoint;
|
||||||
bool m_isDevEnvironment = false;
|
bool m_isDevEnvironment = false;
|
||||||
bool m_isStrictKillSwitchEnabled = false;
|
bool m_isStrictKillSwitchEnabled = false;
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
|
||||||
inline static QString m_proxyUrl;
|
inline static QString m_proxyUrl;
|
||||||
};
|
};
|
||||||
|
|||||||
+32
-32
@@ -390,55 +390,55 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
|||||||
|
|
||||||
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
||||||
|
|
||||||
if (!obj.value("Jc").isNull()) {
|
if (const auto jc = obj.value("Jc"); !jc.isUndefined()) {
|
||||||
config.m_junkPacketCount = obj.value("Jc").toString();
|
config.m_junkPacketCount = jc.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("Jmin").isNull()) {
|
if (const auto jmin = obj.value("Jmin"); !jmin.isUndefined()) {
|
||||||
config.m_junkPacketMinSize = obj.value("Jmin").toString();
|
config.m_junkPacketMinSize = jmin.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("Jmax").isNull()) {
|
if (const auto jmax = obj.value("Jmax"); !jmax.isUndefined()) {
|
||||||
config.m_junkPacketMaxSize = obj.value("Jmax").toString();
|
config.m_junkPacketMaxSize = jmax.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("S1").isNull()) {
|
if (const auto s1 = obj.value("S1"); !s1.isUndefined()) {
|
||||||
config.m_initPacketJunkSize = obj.value("S1").toString();
|
config.m_initPacketJunkSize = s1.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("S2").isNull()) {
|
if (const auto s2 = obj.value("S2"); !s2.isUndefined()) {
|
||||||
config.m_responsePacketJunkSize = obj.value("S2").toString();
|
config.m_responsePacketJunkSize = s2.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("S3").isNull()) {
|
if (const auto s3 = obj.value("S3"); !s3.isUndefined()) {
|
||||||
config.m_cookieReplyPacketJunkSize = obj.value("S3").toString();
|
config.m_cookieReplyPacketJunkSize = s3.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("S4").isNull()) {
|
if (const auto s4 = obj.value("S4"); !s4.isUndefined()) {
|
||||||
config.m_transportPacketJunkSize = obj.value("S4").toString();
|
config.m_transportPacketJunkSize = s4.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!obj.value("H1").isNull()) {
|
if (const auto h1 = obj.value("H1"); !h1.isUndefined()) {
|
||||||
config.m_initPacketMagicHeader = obj.value("H1").toString();
|
config.m_initPacketMagicHeader = h1.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("H2").isNull()) {
|
if (const auto h2 = obj.value("H2"); !h2.isUndefined()) {
|
||||||
config.m_responsePacketMagicHeader = obj.value("H2").toString();
|
config.m_responsePacketMagicHeader = h2.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("H3").isNull()) {
|
if (const auto h3 = obj.value("H3"); !h3.isUndefined()) {
|
||||||
config.m_underloadPacketMagicHeader = obj.value("H3").toString();
|
config.m_underloadPacketMagicHeader = h3.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("H4").isNull()) {
|
if (const auto h4 = obj.value("H4"); !h4.isUndefined()) {
|
||||||
config.m_transportPacketMagicHeader = obj.value("H4").toString();
|
config.m_transportPacketMagicHeader = h4.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!obj.value("I1").isNull()) {
|
if (const auto i1 = obj.value("I1"); !i1.isUndefined()) {
|
||||||
config.m_specialJunk["I1"] = obj.value("I1").toString();
|
config.m_specialJunk["I1"] = i1.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("I2").isNull()) {
|
if (const auto i2 = obj.value("I2"); !i2.isUndefined()) {
|
||||||
config.m_specialJunk["I2"] = obj.value("I2").toString();
|
config.m_specialJunk["I2"] = i2.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("I3").isNull()) {
|
if (const auto i3 = obj.value("I3"); !i3.isUndefined()) {
|
||||||
config.m_specialJunk["I3"] = obj.value("I3").toString();
|
config.m_specialJunk["I3"] = i3.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("I4").isNull()) {
|
if (const auto i4 = obj.value("I4"); !i4.isUndefined()) {
|
||||||
config.m_specialJunk["I4"] = obj.value("I4").toString();
|
config.m_specialJunk["I4"] = i4.toString();
|
||||||
}
|
}
|
||||||
if (!obj.value("I5").isNull()) {
|
if (const auto i5 = obj.value("I5"); !i5.isUndefined()) {
|
||||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
config.m_specialJunk["I5"] = i5.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ namespace
|
|||||||
const char cloudFlareNs2[] = "1.0.0.1";
|
const char cloudFlareNs2[] = "1.0.0.1";
|
||||||
|
|
||||||
constexpr char gatewayEndpoint[] = "http://gw.amnezia.org:80/";
|
constexpr char gatewayEndpoint[] = "http://gw.amnezia.org:80/";
|
||||||
|
constexpr char proxyUrlsKey[] = "Conf/proxyUrls/";
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this)
|
Settings::Settings(QObject *parent) : QObject(parent), m_settings(ORGANIZATION_NAME, APPLICATION_NAME, this)
|
||||||
@@ -526,6 +527,24 @@ void Settings::toggleDevGatewayEnv(bool enabled)
|
|||||||
m_settings.setValue("Conf/devGatewayEnv", enabled);
|
m_settings.setValue("Conf/devGatewayEnv", enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QByteArray Settings::readGatewayProxyUrls(const QString &cacheKey) const
|
||||||
|
{
|
||||||
|
if (cacheKey.isEmpty()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return m_settings.value(QString(proxyUrlsKey) + cacheKey).toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::writeGatewayProxyUrls(const QString &cacheKey, const QByteArray &proxyUrlsEncrypted)
|
||||||
|
{
|
||||||
|
if (cacheKey.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_settings.setValue(QString(proxyUrlsKey) + cacheKey, proxyUrlsEncrypted);
|
||||||
|
}
|
||||||
|
|
||||||
bool Settings::isHomeAdLabelVisible()
|
bool Settings::isHomeAdLabelVisible()
|
||||||
{
|
{
|
||||||
return m_settings.value("Conf/homeAdLabelVisible", true).toBool();
|
return m_settings.value("Conf/homeAdLabelVisible", true).toBool();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QByteArray>
|
||||||
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
@@ -234,6 +235,8 @@ public:
|
|||||||
QString getGatewayEndpoint(bool isTestPurchase = false);
|
QString getGatewayEndpoint(bool isTestPurchase = false);
|
||||||
bool isDevGatewayEnv(bool isTestPurchase = false);
|
bool isDevGatewayEnv(bool isTestPurchase = false);
|
||||||
void toggleDevGatewayEnv(bool enabled);
|
void toggleDevGatewayEnv(bool enabled);
|
||||||
|
QByteArray readGatewayProxyUrls(const QString &cacheKey) const;
|
||||||
|
void writeGatewayProxyUrls(const QString &cacheKey, const QByteArray &proxyUrlsEncrypted);
|
||||||
|
|
||||||
bool isHomeAdLabelVisible();
|
bool isHomeAdLabelVisible();
|
||||||
void disableHomeAdLabel();
|
void disableHomeAdLabel();
|
||||||
|
|||||||
@@ -1027,7 +1027,7 @@ bool ApiConfigsController::updateServiceFromTelegram(const int serverIndex)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(), apiDefs::requestTimeoutMsecs,
|
||||||
m_settings->isStrictKillSwitchEnabled());
|
m_settings->isStrictKillSwitchEnabled(), m_settings);
|
||||||
|
|
||||||
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
|
||||||
auto installationUuid = m_settings->getInstallationUuid(true);
|
auto installationUuid = m_settings->getInstallationUuid(true);
|
||||||
@@ -1273,6 +1273,6 @@ ErrorCode ApiConfigsController::executeRequest(const QString &endpoint, const QJ
|
|||||||
bool isTestPurchase)
|
bool isTestPurchase)
|
||||||
{
|
{
|
||||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
|
||||||
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
|
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled(), m_settings);
|
||||||
return gatewayController.post(endpoint, apiPayload, responseBody);
|
return gatewayController.post(endpoint, apiPayload, responseBody);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ void ApiNewsController::fetchNews(bool showError)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(),
|
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(), m_settings->isDevGatewayEnv(),
|
||||||
apiDefs::requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
|
apiDefs::requestTimeoutMsecs,
|
||||||
|
m_settings->isStrictKillSwitchEnabled(), m_settings);
|
||||||
QJsonObject payload;
|
QJsonObject payload;
|
||||||
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
payload.insert("locale", m_settings->getAppLanguage().name().split("_").first());
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ bool ApiSettingsController::getAccountInfo(bool reload)
|
|||||||
|
|
||||||
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
|
bool isTestPurchase = apiConfig.value(apiDefs::key::isTestPurchase).toBool(false);
|
||||||
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
|
GatewayController gatewayController(m_settings->getGatewayEndpoint(isTestPurchase), m_settings->isDevGatewayEnv(isTestPurchase),
|
||||||
requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled());
|
requestTimeoutMsecs, m_settings->isStrictKillSwitchEnabled(), m_settings);
|
||||||
|
|
||||||
QJsonObject apiPayload;
|
QJsonObject apiPayload;
|
||||||
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
|
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
|
||||||
@@ -110,7 +110,7 @@ void ApiSettingsController::getRenewalLink()
|
|||||||
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(isTestPurchase),
|
auto gatewayController = QSharedPointer<GatewayController>::create(m_settings->getGatewayEndpoint(isTestPurchase),
|
||||||
m_settings->isDevGatewayEnv(isTestPurchase),
|
m_settings->isDevGatewayEnv(isTestPurchase),
|
||||||
requestTimeoutMsecs,
|
requestTimeoutMsecs,
|
||||||
m_settings->isStrictKillSwitchEnabled());
|
m_settings->isStrictKillSwitchEnabled(), m_settings);
|
||||||
|
|
||||||
QJsonObject apiPayload;
|
QJsonObject apiPayload;
|
||||||
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
|
apiPayload[configKey::userCountryCode] = apiConfig.value(configKey::userCountryCode).toString();
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import "../Controls2/TextTypes"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool isRestoringBackup: false
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SettingsController
|
target: SettingsController
|
||||||
|
|
||||||
@@ -138,9 +140,14 @@ PageType {
|
|||||||
textColor: AmneziaStyle.color.paleGray
|
textColor: AmneziaStyle.color.paleGray
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
|
|
||||||
|
enabled: !root.isRestoringBackup
|
||||||
|
|
||||||
text: qsTr("Restore from backup")
|
text: qsTr("Restore from backup")
|
||||||
|
|
||||||
clickedFunc: function() {
|
clickedFunc: function() {
|
||||||
|
if (root.isRestoringBackup) {
|
||||||
|
return
|
||||||
|
}
|
||||||
var filePath = SystemController.getFileName(qsTr("Open backup file"),
|
var filePath = SystemController.getFileName(qsTr("Open backup file"),
|
||||||
qsTr("Backup files (*.backup)"))
|
qsTr("Backup files (*.backup)"))
|
||||||
if (filePath !== "") {
|
if (filePath !== "") {
|
||||||
@@ -152,6 +159,10 @@ PageType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function restoreBackup(filePath) {
|
function restoreBackup(filePath) {
|
||||||
|
if (root.isRestoringBackup) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var headerText = qsTr("Import settings from a backup file?")
|
var headerText = qsTr("Import settings from a backup file?")
|
||||||
var descriptionText = qsTr("All current settings will be reset");
|
var descriptionText = qsTr("All current settings will be reset");
|
||||||
var yesButtonText = qsTr("Continue")
|
var yesButtonText = qsTr("Continue")
|
||||||
@@ -161,9 +172,13 @@ PageType {
|
|||||||
if (ConnectionController.isConnected) {
|
if (ConnectionController.isConnected) {
|
||||||
PageController.showNotificationMessage(qsTr("Cannot restore backup settings during active connection"))
|
PageController.showNotificationMessage(qsTr("Cannot restore backup settings during active connection"))
|
||||||
} else {
|
} else {
|
||||||
|
root.isRestoringBackup = true
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
SettingsController.restoreAppConfig(filePath)
|
Qt.callLater(function() {
|
||||||
PageController.showBusyIndicator(false)
|
SettingsController.restoreAppConfig(filePath)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
root.isRestoringBackup = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var noButtonFunction = function() {
|
var noButtonFunction = function() {
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import "../Config"
|
|||||||
PageType {
|
PageType {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
property bool isRestoringBackup: false
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: ImportController
|
target: ImportController
|
||||||
|
|
||||||
@@ -228,6 +230,8 @@ PageType {
|
|||||||
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
rightImageSource: "qrc:/images/controls/chevron-right.svg"
|
||||||
leftImageSource: imageSource
|
leftImageSource: imageSource
|
||||||
|
|
||||||
|
enabled: !root.isRestoringBackup
|
||||||
|
|
||||||
onClicked: { handler() }
|
onClicked: { handler() }
|
||||||
|
|
||||||
Keys.onEnterPressed: this.clicked()
|
Keys.onEnterPressed: this.clicked()
|
||||||
@@ -314,12 +318,19 @@ PageType {
|
|||||||
property string imageSource: "qrc:/images/controls/archive-restore.svg"
|
property string imageSource: "qrc:/images/controls/archive-restore.svg"
|
||||||
property bool isVisible: PageController.isStartPageVisible()
|
property bool isVisible: PageController.isStartPageVisible()
|
||||||
property var handler: function() {
|
property var handler: function() {
|
||||||
|
if (root.isRestoringBackup) {
|
||||||
|
return
|
||||||
|
}
|
||||||
var filePath = SystemController.getFileName(qsTr("Open backup file"),
|
var filePath = SystemController.getFileName(qsTr("Open backup file"),
|
||||||
qsTr("Backup files (*.backup)"))
|
qsTr("Backup files (*.backup)"))
|
||||||
if (filePath !== "") {
|
if (filePath !== "") {
|
||||||
|
root.isRestoringBackup = true
|
||||||
PageController.showBusyIndicator(true)
|
PageController.showBusyIndicator(true)
|
||||||
SettingsController.restoreAppConfig(filePath)
|
Qt.callLater(function() {
|
||||||
PageController.showBusyIndicator(false)
|
SettingsController.restoreAppConfig(filePath)
|
||||||
|
PageController.showBusyIndicator(false)
|
||||||
|
root.isRestoringBackup = false
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user