diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java index 9f3252ac1..d61c64a9c 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java @@ -106,7 +106,8 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi } @Override - public void onNewIntent(Intent intent) { + public void onNewIntent(final Intent intent) { + super.onNewIntent(intent); if (intent != null) { setIntent(intent); } diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index 5715cd3f1..1e0cf41d3 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -7,24 +7,39 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.util.Log; +import android.view.View; import android.widget.Toast; +import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; +import androidx.databinding.DataBindingUtil; import com.google.common.base.Strings; +import org.jetbrains.annotations.NotNull; + +import java.io.IOException; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; +import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.databinding.ActivityUriHandlerBinding; +import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.utils.ProvisioningUtils; import eu.siacs.conversations.utils.SignupUtils; import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; public class UriHandlerActivity extends AppCompatActivity { @@ -34,7 +49,9 @@ public class UriHandlerActivity extends AppCompatActivity { private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN = 0x6789; private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION = 0x6790; private static final Pattern V_CARD_XMPP_PATTERN = Pattern.compile("\nIMPP([^:]*):(xmpp:.+)\n"); - private boolean handled = false; + private static final Pattern LINK_HEADER_PATTERN = Pattern.compile("<(.*?)>"); + private ActivityUriHandlerBinding binding; + private Call call; public static void scan(final Activity activity) { scan(activity, false); @@ -77,9 +94,7 @@ public class UriHandlerActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - this.handled = savedInstanceState != null && savedInstanceState.getBoolean("handled", false); - getLayoutInflater().inflate(R.layout.toolbar, findViewById(android.R.id.content)); - setSupportActionBar(findViewById(R.id.toolbar)); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler); } @Override @@ -88,23 +103,17 @@ public class UriHandlerActivity extends AppCompatActivity { handleIntent(getIntent()); } - @Override - public void onSaveInstanceState(Bundle savedInstanceState) { - savedInstanceState.putBoolean("handled", this.handled); - super.onSaveInstanceState(savedInstanceState); - } - @Override public void onNewIntent(final Intent intent) { super.onNewIntent(intent); handleIntent(intent); } - private void handleUri(Uri uri) { - handleUri(uri, false); + private boolean handleUri(final Uri uri) { + return handleUri(uri, false); } - private void handleUri(Uri uri, final boolean scanned) { + private boolean handleUri(final Uri uri, final boolean scanned) { final Intent intent; final XmppUri xmppUri = new XmppUri(uri); final List accounts = DatabaseBackend.getInstance(this).getAccountJids(true); @@ -114,19 +123,22 @@ public class UriHandlerActivity extends AppCompatActivity { final Jid jid = xmppUri.getJid(); if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { if (jid.getEscapedLocal() != null && accounts.contains(jid.asBareJid())) { - Toast.makeText(this, R.string.account_already_exists, Toast.LENGTH_LONG).show(); - return; + showError(R.string.account_already_exists); + return false; } intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth); startActivity(intent); - return; + return true; } if (accounts.size() == 0 && xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) { intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); startActivity(intent); - return; + return true; } + } else if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) { + showError(R.string.account_registrations_are_not_supported); + return false; } if (accounts.size() == 0) { @@ -134,15 +146,14 @@ public class UriHandlerActivity extends AppCompatActivity { intent = SignupUtils.getSignUpIntent(this); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); startActivity(intent); + return true; } else { - Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show(); + showError(R.string.invalid_jid); + return false; } - - return; } if (xmppUri.isAction(XmppUri.ACTION_MESSAGE)) { - final Jid jid = xmppUri.getJid(); final String body = xmppUri.getBody(); @@ -177,11 +188,57 @@ public class UriHandlerActivity extends AppCompatActivity { intent.putExtra("scanned", scanned); intent.setData(uri); } else { - Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show(); - return; + showError(R.string.invalid_jid); + return false; } - startActivity(intent); + return true; + } + + private void checkForLinkHeader(final HttpUrl url) { + Log.d(Config.LOGTAG, "checking for link header on " + url); + this.call = HttpConnectionManager.OK_HTTP_CLIENT.newCall(new Request.Builder() + .url(url) + .head() + .build()); + this.call.enqueue(new Callback() { + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + Log.d(Config.LOGTAG, "unable to check HTTP url", e); + showError(R.string.no_xmpp_adddress_found); + } + + @Override + public void onResponse(@NotNull Call call, @NotNull Response response) { + if (response.isSuccessful()) { + final String linkHeader = response.header("Link"); + if (linkHeader != null && processLinkHeader(linkHeader)) { + return; + } + } + showError(R.string.no_xmpp_adddress_found); + } + }); + + } + + private boolean processLinkHeader(final String header) { + final Matcher matcher = LINK_HEADER_PATTERN.matcher(header); + if (matcher.find()) { + final String group = matcher.group(); + final String link = group.substring(1, group.length() - 1); + if (handleUri(Uri.parse(link))) { + finish(); + return true; + } + } + return false; + } + + private void showError(@StringRes int error) { + this.binding.progress.setVisibility(View.INVISIBLE); + this.binding.error.setText(error); + this.binding.error.setVisibility(View.VISIBLE); } private static Class findShareViaAccountClass() { @@ -192,29 +249,33 @@ public class UriHandlerActivity extends AppCompatActivity { } } - private void handleIntent(Intent data) { - if (handled) { + private void handleIntent(final Intent data) { + final String action = data == null ? null : data.getAction(); + if (action == null) { return; } - if (data == null || data.getAction() == null) { - finish(); - return; - } - - handled = true; - - switch (data.getAction()) { + switch (action) { + case Intent.ACTION_MAIN: + binding.progress.setVisibility(call != null && !call.isCanceled() ? View.VISIBLE : View.INVISIBLE); + break; case Intent.ACTION_VIEW: case Intent.ACTION_SENDTO: - handleUri(data.getData()); + if (handleUri(data.getData())) { + finish(); + } break; case ACTION_SCAN_QR_CODE: - Intent intent = new Intent(this, ScanActivity.class); - startActivityForResult(intent, REQUEST_SCAN_QR_CODE); - return; + Log.d(Config.LOGTAG, "scan. allow=" + allowProvisioning()); + setIntent(createMainIntent()); + startActivityForResult(new Intent(this, ScanActivity.class), REQUEST_SCAN_QR_CODE); + break; } + } - finish(); + private Intent createMainIntent() { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.putExtra(EXTRA_ALLOW_PROVISIONING, allowProvisioning()); + return intent; } private boolean allowProvisioning() { @@ -226,6 +287,7 @@ public class UriHandlerActivity extends AppCompatActivity { public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) { super.onActivityResult(requestCode, requestCode, intent); if (requestCode == REQUEST_SCAN_QR_CODE && resultCode == RESULT_OK) { + final boolean allowProvisioning = allowProvisioning(); final String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT); if (Strings.isNullOrEmpty(result)) { finish(); @@ -234,18 +296,34 @@ public class UriHandlerActivity extends AppCompatActivity { if (result.startsWith("BEGIN:VCARD\n")) { final Matcher matcher = V_CARD_XMPP_PATTERN.matcher(result); if (matcher.find()) { - handleUri(Uri.parse(matcher.group(2)), true); + if (handleUri(Uri.parse(matcher.group(2)), true)) { + finish(); + } + } else { + showError(R.string.no_xmpp_adddress_found); } - finish(); return; - } else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning()) { + } else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning) { ProvisioningUtils.provision(this, result); finish(); return; } - handleUri(Uri.parse(result), true); + final Uri uri = Uri.parse(result.trim()); + if (allowProvisioning && "https".equalsIgnoreCase(uri.getScheme()) && !XmppUri.INVITE_DOMAIN.equalsIgnoreCase(uri.getHost())) { + final HttpUrl httpUrl = HttpUrl.parse(uri.toString()); + if (httpUrl != null) { + checkForLinkHeader(httpUrl); + } else { + finish(); + } + } else if (handleUri(uri, true)) { + finish(); + } else { + setIntent(new Intent(Intent.ACTION_VIEW, uri)); + } + } else { + finish(); } - finish(); } private static boolean looksLikeJsonObject(final String input) { diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java index 084982e17..6c3075be9 100644 --- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java +++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java @@ -35,6 +35,8 @@ public class XmppUri { private Map parameters = Collections.emptyMap(); private boolean safeSource = true; + public static final String INVITE_DOMAIN = "conversations.im"; + public XmppUri(final String uri) { try { parse(Uri.parse(uri)); @@ -136,10 +138,10 @@ public class XmppUri { return; } this.uri = uri; - String scheme = uri.getScheme(); - String host = uri.getHost(); + final String scheme = uri.getScheme(); + final String host = uri.getHost(); List segments = uri.getPathSegments(); - if ("https".equalsIgnoreCase(scheme) && "conversations.im".equalsIgnoreCase(host)) { + if ("https".equalsIgnoreCase(scheme) && INVITE_DOMAIN.equalsIgnoreCase(host)) { if (segments.size() >= 2 && segments.get(1).contains("@")) { // sample : https://conversations.im/i/foo@bar.com try { diff --git a/src/main/res/layout/activity_uri_handler.xml b/src/main/res/layout/activity_uri_handler.xml new file mode 100644 index 000000000..9eda73c87 --- /dev/null +++ b/src/main/res/layout/activity_uri_handler.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 6a3815073..4e520e856 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -968,5 +968,7 @@ The backup has been started. You’ll get a notification once it has been completed. Unable to enable video. Plain text document + Account registrations are not supported + No XMPP address found