summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2021-09-28 01:24:09 +0200
committerArne Schwabe <arne@rfc2549.org>2021-10-01 19:56:39 +0200
commitf6227082f1c70f0443e0f5d6a5f10c2838e1b7ee (patch)
treea44ba5d3290fd4c292097bea3566742d67ffb016
parent9e704d04dc7f2f93bddf85d371772340fa5af0b1 (diff)
Implement using compat-mode
-rw-r--r--main/src/main/java/de/blinkt/openvpn/VpnProfile.java31
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java28
-rw-r--r--main/src/main/res/values/arrays.xml6
-rwxr-xr-xmain/src/main/res/values/strings.xml2
-rw-r--r--main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt46
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt13
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java6
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/fragments/Utils.java299
-rw-r--r--main/src/ui/java/de/blinkt/openvpn/fragments/Utils.kt261
-rw-r--r--main/src/ui/res/layout/basic_settings.xml11
-rw-r--r--main/src/ui/res/layout/config_converter.xml14
11 files changed, 389 insertions, 328 deletions
diff --git a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 5c2edd6b..9146af00 100644
--- a/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/main/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -164,16 +164,17 @@ public class VpnProfile implements Serializable, Cloneable {
public String mServerPort = "1194";
public boolean mUseUdp = true;
public boolean mTemporaryProfile = false;
+ public String mDataCiphers = "";
+ public boolean mBlockUnusedAddressFamilies = true;
+ public boolean mCheckPeerFingerprint = false;
+ public String mPeerFingerPrints = "";
+ public int mCompatMode = 0;
+
private transient PrivateKey mPrivateKey;
// Public attributes, since I got mad with getter/setter
// set members to default values
private UUID mUuid;
private int mProfileVersion;
- public String mDataCiphers = "";
-
- public boolean mBlockUnusedAddressFamilies =true;
- public boolean mCheckPeerFingerprint = false;
- public String mPeerFingerPrints = "";
public VpnProfile(String name) {
mUuid = UUID.randomUUID();
@@ -313,10 +314,9 @@ public class VpnProfile implements Serializable, Cloneable {
c.mProxyType = Connection.ProxyType.NONE;
case 7:
if (mAllowAppVpnBypass)
- mBlockUnusedAddressFamilies = !mAllowAppVpnBypass;
+ mBlockUnusedAddressFamilies = false;
case 8:
- if (!TextUtils.isEmpty(mCipher) && !mCipher.equals("AES-256-GCM") && !mCipher.equals("AES-128-GCM"))
- {
+ if (!TextUtils.isEmpty(mCipher) && !mCipher.equals("AES-256-GCM") && !mCipher.equals("AES-128-GCM")) {
mDataCiphers = "AES-256-GCM:AES-128-GCM:" + mCipher;
}
default:
@@ -457,8 +457,7 @@ public class VpnProfile implements Serializable, Cloneable {
case VpnProfile.TYPE_PKCS12:
cfg.append(insertFileData("pkcs12", mPKCS12Filename));
- if (!TextUtils.isEmpty(mCaFilename))
- {
+ if (!TextUtils.isEmpty(mCaFilename)) {
cfg.append(insertFileData("ca", mCaFilename));
}
break;
@@ -640,11 +639,19 @@ public class VpnProfile implements Serializable, Cloneable {
cfg.append("remote-cert-tls server\n");
}
- if (!TextUtils.isEmpty(mDataCiphers))
- {
+ if (!TextUtils.isEmpty(mDataCiphers)) {
cfg.append("data-ciphers ").append(mDataCiphers).append("\n");
}
+ if (mCompatMode > 0)
+ {
+ int major = mCompatMode/10000;
+ int minor = mCompatMode % 10000/100;
+ int patch = mCompatMode % 100;
+ cfg.append(String.format(Locale.US, "compat-mode %d.%d.%d\n", major, minor, patch));
+
+ }
+
if (!TextUtils.isEmpty(mCipher)) {
cfg.append("cipher ").append(mCipher).append("\n");
}
diff --git a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
index d25c20c0..a1b1bcb6 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
@@ -538,24 +538,26 @@ public class ConfigParser {
if (cipher != null)
np.mCipher = cipher.get(1);
- if (data_ciphers == null)
- {
- data_ciphers = ncp_ciphers;
- }
-
- /* The world is not yet ready to only use data-ciphers, add --cipher to data-ciphers
- * for now on import */
if (data_ciphers != null)
{
np.mDataCiphers = data_ciphers.get(1);
+ }
+ else if (ncp_ciphers != null)
+ {
+ np.mDataCiphers = ncp_ciphers.get(1);
+ }
- if (!TextUtils.isEmpty(np.mCipher) && !np.mDataCiphers.contains(np.mCipher))
- {
- np.mDataCiphers += ":" + np.mCipher;
- }
- } else if (!TextUtils.isEmpty(np.mCipher) && !np.mCipher.equals("AES-128-GCM") && !np.mCipher.equals("AES-256"))
+
+ Vector<String> compatmode = getOption("compat-mode", 1, 1);
+ if (compatmode != null)
{
- np.mDataCiphers += "AES-256-GCM:AES-128-GCM:" + np.mCipher;
+ Scanner versionScanner = new Scanner(compatmode.get(1));
+ versionScanner.useDelimiter("\\.");
+ int major = versionScanner.nextInt();
+ int minor = versionScanner.nextInt();
+ int patch = versionScanner.nextInt();
+
+ np.mCompatMode = major * 10000 + minor * 100 + patch;
}
Vector<String> auth = getOption("auth", 1, 1);
diff --git a/main/src/main/res/values/arrays.xml b/main/src/main/res/values/arrays.xml
index 3b7c42e1..97c10ff7 100644
--- a/main/src/main/res/values/arrays.xml
+++ b/main/src/main/res/values/arrays.xml
@@ -36,4 +36,10 @@
<item>Disconnect, keep password</item>
<item>Ignore, retry</item>
</string-array>
+ <string-array name="compat_mode">
+ <item>Modern defaults</item>
+ <item>OpenVPN 2.5.x peers</item>
+ <item>OpenVPN 2.4.x peers</item>
+ <item>OpenVPN 2.3.x and older peers</item>
+ </string-array>
</resources>
diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml
index 7cab17e4..2de43eb2 100755
--- a/main/src/main/res/values/strings.xml
+++ b/main/src/main/res/values/strings.xml
@@ -509,5 +509,7 @@
<string name="trigger_pending_auth_dialog">Press here open a window to enter additional required authentication</string>
<string name="import_from_access_server">OpenVPN Access Server</string>
<string name="import_from_URL">URL</string>
+ <string name="compatmode">Compatiblity Mode</string>
+ <string name="compat_mode_label">Compatibility mode</string>
</resources>
diff --git a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt
index fbaa4be2..c9dd64a2 100644
--- a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt
+++ b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.kt
@@ -9,6 +9,7 @@ import android.content.Context
import android.os.Build
import androidx.test.core.app.ApplicationProvider
import de.blinkt.openvpn.R
+import de.blinkt.openvpn.fragments.Utils
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
@@ -145,7 +146,7 @@ class TestConfigParser {
cp.parseConfig(StringReader(config4))
val vp4 = cp.convertProfile()
- Assert.assertEquals("AES-128-GCM:AES-256-GCM:CHACHA20-POLY1305:BF-CBC", vp4.mDataCiphers)
+ Assert.assertEquals("AES-128-GCM:AES-256-GCM:CHACHA20-POLY1305", vp4.mDataCiphers)
@@ -153,6 +154,49 @@ class TestConfigParser {
@Test
+ fun testCompatmodeImport() {
+ val config = ("client\n"
+ + "tun-mtu 1234\n" +
+ "<connection>\n" +
+ "remote foo.bar\n" +
+ "tun-mtu 1222\n" +
+ "</connection>\n" +
+ "<cert>\nfakecert\n</cert>\n" +
+ "<key>\nfakekey\n</key>\n" +
+ "route 8.8.8.8 255.255.255.255 net_gateway\n")
+ val c:Context = ApplicationProvider.getApplicationContext()
+
+ val config1 = config + "compat-mode 2.7.7\n"
+
+ val cp = ConfigParser()
+ cp.parseConfig(StringReader(config1))
+ val vp = cp.convertProfile()
+
+ Assert.assertEquals(20707, vp.mCompatMode)
+ Assert.assertEquals(0,Utils.mapCompatVer(vp.mCompatMode))
+
+
+ val config2 = config + "compat-mode 2.4.0\n"
+
+
+ cp.parseConfig(StringReader(config2))
+ val vp2 = cp.convertProfile()
+ Assert.assertEquals(20400, vp2.mCompatMode)
+ Assert.assertEquals(2,Utils.mapCompatVer(vp2.mCompatMode))
+ val conf2 = vp2.getConfigFile(c, false)
+ Assert.assertTrue(conf2.contains("compat-mode 2.4.0"));
+
+ val config3 = config + "compat-mode 1.17.23\n";
+ cp.parseConfig(StringReader(config3))
+ val vp3 = cp.convertProfile()
+ Assert.assertEquals(11723, vp3.mCompatMode)
+ Assert.assertEquals(3,Utils.mapCompatVer(vp3.mCompatMode))
+
+ val conf = vp3.getConfigFile(c, false)
+ Assert.assertTrue(conf.contains("compat-mode 1.17.23"));
+ }
+
+ @Test
@Throws(IOException::class, ConfigParser.ConfigParseError::class)
fun testSockProxyImport() {
val proxy = "ca baz\n" +
diff --git a/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt
index d01797f2..6ccf4c8a 100644
--- a/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt
+++ b/main/src/ui/java/de/blinkt/openvpn/activities/ConfigConverter.kt
@@ -55,6 +55,8 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
private val mLogEntries = Vector<String>()
private var mSourceUri: Uri? = null
private lateinit var mProfilename: EditText
+ private lateinit var mCompatmode: Spinner
+ private lateinit var mCompatmodeLabel: TextView
private var mImportTask: AsyncTask<Void, Void, Int>? = null
private lateinit var mLogLayout: LinearLayout
private lateinit var mProfilenameLabel: TextView
@@ -124,6 +126,8 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
return true
}
+ mResult!!.mCompatMode = Utils.mapCompatMode(mCompatmode.selectedItemPosition)
+
val `in` = installPKCS12()
if (`in` != null)
@@ -573,12 +577,17 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
mProfilename = findViewById<View>(R.id.profilename) as EditText
mProfilenameLabel = findViewById<View>(R.id.profilename_label) as TextView
+ mCompatmode = findViewById(R.id.compatmode) as Spinner
+ mCompatmodeLabel = findViewById(R.id.compatmode_label) as TextView
+
+
if (savedInstanceState != null && savedInstanceState.containsKey(VPNPROFILE)) {
mResult = savedInstanceState.getSerializable(VPNPROFILE) as VpnProfile?
mAliasName = savedInstanceState.getString("mAliasName")
mEmbeddedPwFile = savedInstanceState.getString("pwfile")
mSourceUri = savedInstanceState.getParcelable("mSourceUri")
mProfilename.setText(mResult!!.mName)
+ mCompatmode.setSelection(Utils.mapCompatVer(mResult!!.mCompatMode))
if (savedInstanceState.containsKey("logentries")) {
@@ -714,6 +723,10 @@ class ConfigConverter : BaseActivity(), FileSelectCallback, View.OnClickListener
mProfilenameLabel.visibility = View.VISIBLE
mProfilename.setText(mResult!!.name)
+ mCompatmode.visibility = View.VISIBLE
+ mCompatmodeLabel.visibility = View.VISIBLE
+ mCompatmode.setSelection(Utils.mapCompatVer(mResult!!.mCompatMode))
+
log(R.string.import_done)
}
}
diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java b/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java
index 0afb754d..0899dd13 100644
--- a/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java
+++ b/main/src/ui/java/de/blinkt/openvpn/fragments/Settings_Basic.java
@@ -17,7 +17,6 @@ import android.widget.AdapterView.OnItemSelectedListener;
import de.blinkt.openvpn.R;
import de.blinkt.openvpn.R.id;
import de.blinkt.openvpn.VpnProfile;
-import de.blinkt.openvpn.core.ProfileManager;
import de.blinkt.openvpn.views.FileSelectLayout;
public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSelectedListener, FileSelectLayout.FileSelectCallback, CompoundButton.OnCheckedChangeListener {
@@ -28,6 +27,7 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe
private FileSelectLayout mClientKey;
private CheckBox mUseLzo;
private Spinner mType;
+ private Spinner mCompatMode;
private FileSelectLayout mpkcs12;
private FileSelectLayout mCrlFile;
private TextView mPKCS12Password;
@@ -69,6 +69,7 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe
mCrlFile = mView.findViewById(id.crlfile);
mUseLzo = mView.findViewById(id.lzo);
mType = mView.findViewById(id.type);
+ mCompatMode = mView.findViewById(id.compatmode);
mPKCS12Password = mView.findViewById(id.pkcs12password);
mEnablePeerFingerprint = mView.findViewById(id.enable_peer_fingerprint);
mPeerFingerprints = mView.findViewById(id.peer_fingerprint);
@@ -191,6 +192,7 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe
mUseLzo.setChecked(mProfile.mUseLzo);
mType.setSelection(mProfile.mAuthenticationType);
+ mCompatMode.setSelection(Utils.mapCompatVer(mProfile.mCompatMode));
mpkcs12.setData(mProfile.mPKCS12Filename, getActivity());
mPKCS12Password.setText(mProfile.mPKCS12Password);
mUserName.setText(mProfile.mUsername);
@@ -220,7 +222,7 @@ public class Settings_Basic extends KeyChainSettingsFragment implements OnItemSe
mProfile.mAuthRetry = mAuthRetry.getSelectedItemPosition();
mProfile.mCheckPeerFingerprint = mEnablePeerFingerprint.isChecked();
mProfile.mPeerFingerPrints = mPeerFingerprints.getText().toString();
-
+ mProfile.mCompatMode = Utils.mapCompatMode(mCompatMode.getSelectedItemPosition());
}
@Override
diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.java b/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.java
deleted file mode 100644
index 3692624b..00000000
--- a/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (c) 2012-2016 Arne Schwabe
- * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
- */
-
-package de.blinkt.openvpn.fragments;
-
-import android.annotation.TargetApi;
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.provider.OpenableColumns;
-import android.util.Base64;
-import android.webkit.MimeTypeMap;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.List;
-import java.util.TreeSet;
-import java.util.Vector;
-
-import de.blinkt.openvpn.VpnProfile;
-import de.blinkt.openvpn.core.Preferences;
-
-public class Utils {
-
-
- @TargetApi(Build.VERSION_CODES.KITKAT)
- public static Intent getFilePickerIntent(Context c, FileType fileType) {
- Intent i = new Intent(Intent.ACTION_GET_CONTENT);
- i.addCategory(Intent.CATEGORY_OPENABLE);
- TreeSet<String> supportedMimeTypes = new TreeSet<String>();
- Vector<String> extensions = new Vector<>();
-
- switch (fileType) {
- case PKCS12:
- i.setType("application/x-pkcs12");
- supportedMimeTypes.add("application/x-pkcs12");
- extensions.add("p12");
- extensions.add("pfx");
- break;
- case CLIENT_CERTIFICATE:
- case CA_CERTIFICATE:
- i.setType("application/x-pem-file");
- supportedMimeTypes.add("application/x-x509-ca-cert");
- supportedMimeTypes.add("application/x-x509-user-cert");
- supportedMimeTypes.add("application/x-pem-file");
- supportedMimeTypes.add("application/pkix-cert");
- supportedMimeTypes.add("text/plain");
-
- extensions.add("pem");
- extensions.add("crt");
- extensions.add("cer");
- break;
- case KEYFILE:
- i.setType("application/x-pem-file");
- supportedMimeTypes.add("application/x-pem-file");
- supportedMimeTypes.add("application/pkcs8");
-
- // Google drive ....
- supportedMimeTypes.add("application/x-iwork-keynote-sffkey");
- extensions.add("key");
- break;
-
- case TLS_AUTH_FILE:
- i.setType("text/plain");
-
- // Backup ....
- supportedMimeTypes.add("application/pkcs8");
- // Google Drive is kind of crazy .....
- supportedMimeTypes.add("application/x-iwork-keynote-sffkey");
-
- extensions.add("txt");
- extensions.add("key");
- break;
-
- case OVPN_CONFIG:
- i.setType("application/x-openvpn-profile");
- supportedMimeTypes.add("application/x-openvpn-profile");
- supportedMimeTypes.add("application/openvpn-profile");
- supportedMimeTypes.add("application/ovpn");
- supportedMimeTypes.add("text/plain");
- extensions.add("ovpn");
- extensions.add("conf");
- break;
-
- case CRL_FILE:
- supportedMimeTypes.add("application/x-pkcs7-crl");
- supportedMimeTypes.add("application/pkix-crl");
- extensions.add("crl");
- break;
-
- case USERPW_FILE:
- i.setType("text/plain");
- supportedMimeTypes.add("text/plain");
- break;
- }
-
- MimeTypeMap mtm = MimeTypeMap.getSingleton();
-
- for (String ext : extensions) {
- String mimeType = mtm.getMimeTypeFromExtension(ext);
- if (mimeType != null)
- supportedMimeTypes.add(mimeType);
- }
-
- // Always add this as fallback
- supportedMimeTypes.add("application/octet-stream");
-
- i.putExtra(Intent.EXTRA_MIME_TYPES, supportedMimeTypes.toArray(new String[supportedMimeTypes.size()]));
-
- // People don't know that this is actually a system setting. Override it ...
- // DocumentsContract.EXTRA_SHOW_ADVANCED is hidden
- i.putExtra("android.content.extra.SHOW_ADVANCED", true);
-
- /* Samsung has decided to do something strange, on stock Android GET_CONTENT opens the document UI */
- /* fist try with documentsui */
- if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N)
- i.setPackage("com.android.documentsui");
-
-
- /*
- * Android 11 is much stricter about allowing what to query. Since the app has the
- * QUERY_ALL permission we can still check on Android 11 but otherwise we would just
- * assume the documents ui to be always there:
- */
-
- /*
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- return i;
- }*/
-
-
-
- //noinspection ConstantConditions
- if (!isIntentAvailable(c,i)) {
- i.setAction(Intent.ACTION_OPEN_DOCUMENT);
- i.setPackage(null);
-
- // Check for really broken devices ... :(
- if (!isIntentAvailable(c,i)) {
- return null;
- }
- }
-
-
- /*
- final PackageManager packageManager = c.getPackageManager();
- ResolveInfo list = packageManager.resolveActivity(i, 0);
-
- Toast.makeText(c, "Starting package: "+ list.activityInfo.packageName
- + "with ACTION " + i.getAction(), Toast.LENGTH_LONG).show();
-
- */
- return i;
- }
-
- public static boolean alwaysUseOldFileChooser(Context c)
- {
- /* Android P does not allow access to the file storage anymore */
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P)
- return false;
-
- SharedPreferences prefs = Preferences.getDefaultSharedPreferences(c);
- return prefs.getBoolean("useInternalFileSelector", false);
- }
-
- public static boolean isIntentAvailable(Context context, Intent i) {
- final PackageManager packageManager = context.getPackageManager();
- List<ResolveInfo> list =
- packageManager.queryIntentActivities(i,
- PackageManager.MATCH_DEFAULT_ONLY);
-
- // Ignore the Android TV framework app in the list
- int size = list.size();
- for (ResolveInfo ri: list)
- {
- // Ignore stub apps
- if ("com.google.android.tv.frameworkpackagestubs".equals(ri.activityInfo.packageName))
- {
- size--;
- }
- }
-
- return size > 0;
- }
-
-
- public enum FileType {
- PKCS12(0),
- CLIENT_CERTIFICATE(1),
- CA_CERTIFICATE(2),
- OVPN_CONFIG(3),
- KEYFILE(4),
- TLS_AUTH_FILE(5),
- USERPW_FILE(6),
- CRL_FILE(7);
-
- private int value;
-
- FileType(int i) {
- value = i;
- }
-
- public static FileType getFileTypeByValue(int value) {
- switch (value) {
- case 0:
- return PKCS12;
- case 1:
- return CLIENT_CERTIFICATE;
- case 2:
- return CA_CERTIFICATE;
- case 3:
- return OVPN_CONFIG;
- case 4:
- return KEYFILE;
- case 5:
- return TLS_AUTH_FILE;
- case 6:
- return USERPW_FILE;
- case 7:
- return CRL_FILE;
- default:
- return null;
- }
- }
-
- public int getValue() {
- return value;
- }
- }
-
- static private byte[] readBytesFromStream(InputStream input) throws IOException {
-
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
-
- int nRead;
- byte[] data = new byte[16384];
-
- ;
-
- long totalread = 0;
- while ((nRead = input.read(data, 0, data.length)) != -1 && totalread <VpnProfile.MAX_EMBED_FILE_SIZE ) {
- buffer.write(data, 0, nRead);
- totalread+=nRead;
- }
-
- buffer.flush();
- input.close();
- return buffer.toByteArray();
- }
-
- public static String getFilePickerResult(FileType ft, Intent result, Context c) throws IOException, SecurityException {
-
- Uri uri = result.getData();
- if (uri == null)
- return null;
-
- byte[] fileData = readBytesFromStream(c.getContentResolver().openInputStream(uri));
- String newData = null;
-
- Cursor cursor = c.getContentResolver().query(uri, null, null, null, null);
-
- String prefix = "";
- try {
- if (cursor!=null && cursor.moveToFirst()) {
- int cidx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
- if (cidx != -1) {
- String displayName = cursor.getString(cidx);
-
- if (!displayName.contains(VpnProfile.INLINE_TAG) && !displayName.contains(VpnProfile.DISPLAYNAME_TAG))
- prefix = VpnProfile.DISPLAYNAME_TAG + displayName;
- }
- }
- } finally {
- if(cursor!=null)
- cursor.close();
- }
-
- switch (ft) {
- case PKCS12:
- newData = Base64.encodeToString(fileData, Base64.DEFAULT);
- break;
- default:
- newData = new String(fileData, "UTF-8");
- break;
- }
-
- return prefix + VpnProfile.INLINE_TAG + newData;
- }
-
-}
diff --git a/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.kt b/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.kt
new file mode 100644
index 00000000..64a8d3ba
--- /dev/null
+++ b/main/src/ui/java/de/blinkt/openvpn/fragments/Utils.kt
@@ -0,0 +1,261 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+package de.blinkt.openvpn.fragments
+
+import android.annotation.TargetApi
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.os.Build
+import android.provider.OpenableColumns
+import android.util.Base64
+import android.webkit.MimeTypeMap
+import kotlin.Throws
+import de.blinkt.openvpn.VpnProfile
+import de.blinkt.openvpn.core.Preferences
+import java.io.ByteArrayOutputStream
+import java.io.IOException
+import java.io.InputStream
+import java.nio.charset.Charset
+import java.util.*
+
+object Utils {
+ @JvmStatic
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ fun getFilePickerIntent(c: Context, fileType: FileType?): Intent? {
+ val i = Intent(Intent.ACTION_GET_CONTENT)
+ i.addCategory(Intent.CATEGORY_OPENABLE)
+ val supportedMimeTypes = TreeSet<String>()
+ val extensions = Vector<String>()
+ when (fileType) {
+ FileType.PKCS12 -> {
+ i.type = "application/x-pkcs12"
+ supportedMimeTypes.add("application/x-pkcs12")
+ extensions.add("p12")
+ extensions.add("pfx")
+ }
+ FileType.CLIENT_CERTIFICATE, FileType.CA_CERTIFICATE -> {
+ i.type = "application/x-pem-file"
+ supportedMimeTypes.add("application/x-x509-ca-cert")
+ supportedMimeTypes.add("application/x-x509-user-cert")
+ supportedMimeTypes.add("application/x-pem-file")
+ supportedMimeTypes.add("application/pkix-cert")
+ supportedMimeTypes.add("text/plain")
+ extensions.add("pem")
+ extensions.add("crt")
+ extensions.add("cer")
+ }
+ FileType.KEYFILE -> {
+ i.type = "application/x-pem-file"
+ supportedMimeTypes.add("application/x-pem-file")
+ supportedMimeTypes.add("application/pkcs8")
+
+ // Google drive ....
+ supportedMimeTypes.add("application/x-iwork-keynote-sffkey")
+ extensions.add("key")
+ }
+ FileType.TLS_AUTH_FILE -> {
+ i.type = "text/plain"
+
+ // Backup ....
+ supportedMimeTypes.add("application/pkcs8")
+ // Google Drive is kind of crazy .....
+ supportedMimeTypes.add("application/x-iwork-keynote-sffkey")
+ extensions.add("txt")
+ extensions.add("key")
+ }
+ FileType.OVPN_CONFIG -> {
+ i.type = "application/x-openvpn-profile"
+ supportedMimeTypes.add("application/x-openvpn-profile")
+ supportedMimeTypes.add("application/openvpn-profile")
+ supportedMimeTypes.add("application/ovpn")
+ supportedMimeTypes.add("text/plain")
+ extensions.add("ovpn")
+ extensions.add("conf")
+ }
+ FileType.CRL_FILE -> {
+ supportedMimeTypes.add("application/x-pkcs7-crl")
+ supportedMimeTypes.add("application/pkix-crl")
+ extensions.add("crl")
+ }
+ FileType.USERPW_FILE -> {
+ i.type = "text/plain"
+ supportedMimeTypes.add("text/plain")
+ }
+ }
+ val mtm = MimeTypeMap.getSingleton()
+ for (ext in extensions) {
+ val mimeType = mtm.getMimeTypeFromExtension(ext)
+ if (mimeType != null) supportedMimeTypes.add(mimeType)
+ }
+
+ // Always add this as fallback
+ supportedMimeTypes.add("application/octet-stream")
+ i.putExtra(Intent.EXTRA_MIME_TYPES, supportedMimeTypes.toTypedArray())
+
+ // People don't know that this is actually a system setting. Override it ...
+ // DocumentsContract.EXTRA_SHOW_ADVANCED is hidden
+ i.putExtra("android.content.extra.SHOW_ADVANCED", true)
+
+ /* Samsung has decided to do something strange, on stock Android GET_CONTENT opens the document UI */
+ /* fist try with documentsui */if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) i.setPackage(
+ "com.android.documentsui"
+ )
+
+
+ /*
+ * Android 11 is much stricter about allowing what to query. Since the app has the
+ * QUERY_ALL permission we can still check on Android 11 but otherwise we would just
+ * assume the documents ui to be always there:
+ */
+
+ /*
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ return i;
+ }*/if (!isIntentAvailable(c, i)) {
+ i.action = Intent.ACTION_OPEN_DOCUMENT
+ i.setPackage(null)
+
+ // Check for really broken devices ... :(
+ if (!isIntentAvailable(c, i)) {
+ return null
+ }
+ }
+
+
+ /*
+ final PackageManager packageManager = c.getPackageManager();
+ ResolveInfo list = packageManager.resolveActivity(i, 0);
+
+ Toast.makeText(c, "Starting package: "+ list.activityInfo.packageName
+ + "with ACTION " + i.getAction(), Toast.LENGTH_LONG).show();
+
+ */return i
+ }
+
+ @JvmStatic
+ fun alwaysUseOldFileChooser(c: Context?): Boolean {
+ /* Android P does not allow access to the file storage anymore */
+ if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) return false
+ val prefs = Preferences.getDefaultSharedPreferences(c)
+ return prefs.getBoolean("useInternalFileSelector", false)
+ }
+
+ fun isIntentAvailable(context: Context, i: Intent?): Boolean {
+ val packageManager = context.packageManager
+ val list = packageManager.queryIntentActivities(
+ i!!,
+ PackageManager.MATCH_DEFAULT_ONLY
+ )
+
+ // Ignore the Android TV framework app in the list
+ var size = list.size
+ for (ri in list) {
+ // Ignore stub apps
+ if ("com.google.android.tv.frameworkpackagestubs" == ri.activityInfo.packageName) {
+ size--
+ }
+ }
+ return size > 0
+ }
+
+ @Throws(IOException::class)
+ private fun readBytesFromStream(input: InputStream?): ByteArray {
+ val buffer = ByteArrayOutputStream()
+ var nRead: Int
+ val data = ByteArray(16384)
+ var totalread: Long = 0
+ while (input!!.read(data, 0, data.size)
+ .also { nRead = it } != -1 && totalread < VpnProfile.MAX_EMBED_FILE_SIZE
+ ) {
+ buffer.write(data, 0, nRead)
+ totalread += nRead.toLong()
+ }
+ buffer.flush()
+ input.close()
+ return buffer.toByteArray()
+ }
+
+ @JvmStatic
+ @Throws(IOException::class, SecurityException::class)
+ fun getFilePickerResult(ft: FileType?, result: Intent, c: Context): String? {
+ val uri = result.data ?: return null
+ val fileData = readBytesFromStream(c.contentResolver.openInputStream(uri))
+ var newData: String? = null
+ val cursor = c.contentResolver.query(uri, null, null, null, null)
+ var prefix = ""
+ try {
+ if (cursor != null && cursor.moveToFirst()) {
+ val cidx = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
+ if (cidx != -1) {
+ val displayName = cursor.getString(cidx)
+ if (!displayName.contains(VpnProfile.INLINE_TAG) && !displayName.contains(
+ VpnProfile.DISPLAYNAME_TAG
+ )
+ ) prefix = VpnProfile.DISPLAYNAME_TAG + displayName
+ }
+ }
+ } finally {
+ cursor?.close()
+ }
+ newData = when (ft) {
+ FileType.PKCS12 -> Base64.encodeToString(
+ fileData,
+ Base64.DEFAULT
+ )
+ else -> String(fileData, Charset.forName("UTF-8"))
+ }
+ return prefix + VpnProfile.INLINE_TAG + newData
+ }
+
+ enum class FileType(val value: Int) {
+ PKCS12(0), CLIENT_CERTIFICATE(1), CA_CERTIFICATE(2), OVPN_CONFIG(3), KEYFILE(4), TLS_AUTH_FILE(
+ 5
+ ),
+ USERPW_FILE(6), CRL_FILE(7);
+
+ companion object {
+ fun getFileTypeByValue(value: Int): FileType? {
+ return when (value) {
+ 0 -> PKCS12
+ 1 -> CLIENT_CERTIFICATE
+ 2 -> CA_CERTIFICATE
+ 3 -> OVPN_CONFIG
+ 4 -> KEYFILE
+ 5 -> TLS_AUTH_FILE
+ 6 -> USERPW_FILE
+ 7 -> CRL_FILE
+ else -> null
+ }
+ }
+ }
+ }
+
+ /* These functions make assumptions about the R.arrays.compat_mode contents */
+ @JvmStatic
+ fun mapCompatVer(ver: Int): Int {
+ if (ver == 0 || ver >= 20600)
+ return 0
+ else if (ver < 20400)
+ return 3
+ else if (ver < 20500)
+ return 2
+ else if (ver < 20600)
+ return 1
+ return 0
+ }
+
+ @JvmStatic
+
+ fun mapCompatMode(mode: Int): Int {
+ when (mode) {
+ 0 -> return 0
+ 1 -> return 20500
+ 2 -> return 20400
+ 3 -> return 20300
+ else -> return 0
+ }
+ }
+} \ No newline at end of file
diff --git a/main/src/ui/res/layout/basic_settings.xml b/main/src/ui/res/layout/basic_settings.xml
index 6763e52f..dd2ed25e 100644
--- a/main/src/ui/res/layout/basic_settings.xml
+++ b/main/src/ui/res/layout/basic_settings.xml
@@ -28,7 +28,16 @@
style="@style/item"
android:inputType="text" />
-
+ <TextView
+ style="@style/item"
+ android:text="@string/compat_mode_label"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ <Spinner
+ android:id="@+id/compatmode"
+ style="@style/item"
+ android:prompt="@string/compatmode"
+ android:entries="@array/compat_mode"
+ />
<CheckBox
android:id="@+id/lzo"
diff --git a/main/src/ui/res/layout/config_converter.xml b/main/src/ui/res/layout/config_converter.xml
index 4070ff7c..b980dc59 100644
--- a/main/src/ui/res/layout/config_converter.xml
+++ b/main/src/ui/res/layout/config_converter.xml
@@ -36,6 +36,20 @@
android:inputType="text" />
<TextView
+ style="@style/item"
+ android:id="@+id/compatmode_label"
+ android:text="@string/compat_mode_label"
+ android:visibility="gone"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ <Spinner
+ android:id="@+id/compatmode"
+ style="@style/item"
+ android:visibility="gone"
+ android:prompt="@string/compatmode"
+ android:entries="@array/compat_mode"
+ />
+
+ <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/files_missing_hint"