diff --git a/app/build.gradle b/app/build.gradle
index 0ae8adf0..e4d0292f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -82,6 +82,11 @@ dependencies {
implementation 'com.nulab-inc:zxcvbn:1.2.3'
+ implementation 'dnsjava:dnsjava:2.1.8'
+ //implementation "org.minidns:minidns-hla:0.3.2"
+ implementation 'org.jitsi:dnssecjava:1.1.3'
+ implementation 'org.slf4j:slf4j-nop:1.7.25'
+
testImplementation "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 805781ce..ef4be985 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
package="com.m2049r.xmrwallet">
+
diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
index 8d8cdc0d..8308fe60 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
@@ -273,6 +273,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
break;
case Toolbar.BUTTON_CANCEL:
onDisposeRequest();
+ Helper.hideKeyboard(WalletActivity.this);
WalletActivity.super.onBackPressed();
break;
case Toolbar.BUTTON_CLOSE:
@@ -920,7 +921,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
drawer.closeDrawer(GravityCompat.START);
return;
}
-
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof OnBackPressedListener) {
if (!((OnBackPressedListener) fragment).onBackPressed()) {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java b/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java
index 635bbadf..10b13b29 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/data/BarcodeData.java
@@ -20,6 +20,7 @@ import android.net.Uri;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
+import com.m2049r.xmrwallet.util.OpenAliasHelper;
import java.util.HashMap;
import java.util.Map;
@@ -32,6 +33,9 @@ public class BarcodeData {
public static final String XMR_AMOUNT = "tx_amount";
public static final String XMR_DESCRIPTION = "tx_description";
+ public static final String OA_XMR_ASSET = "xmr";
+ public static final String OA_BTC_ASSET = "btc";
+
static final String BTC_SCHEME = "bitcoin:";
static final String BTC_AMOUNT = "amount";
@@ -40,10 +44,12 @@ public class BarcodeData {
}
public Asset asset = null;
+ public String addressName = null;
public String address = null;
public String paymentId = null;
public String amount = null;
public String description = null;
+ public boolean isSecure = true;
public BarcodeData(String uri) {
this.asset = asset;
@@ -76,6 +82,14 @@ public class BarcodeData {
this.amount = amount;
}
+ public void setAddressName(String name) {
+ addressName = name;
+ }
+
+ public void isSecure(boolean isSecure) {
+ this.isSecure = isSecure;
+ }
+
public Uri getUri() {
return Uri.parse(getUriString());
}
@@ -95,7 +109,7 @@ public class BarcodeData {
first = false;
sb.append(BarcodeData.XMR_DESCRIPTION).append('=').append(Uri.encode(description));
}
- if (!amount.isEmpty()) {
+ if ((amount != null) && !amount.isEmpty()) {
sb.append(first ? "?" : "&");
sb.append(BarcodeData.XMR_AMOUNT).append('=').append(amount);
}
@@ -113,10 +127,14 @@ public class BarcodeData {
if (bcData == null) {
bcData = parseBitcoinUri(qrCode);
}
- // check for naked btc addres
+ // check for naked btc address
if (bcData == null) {
bcData = parseBitcoinNaked(qrCode);
}
+ // check for OpenAlias
+ if (bcData == null) {
+ bcData = parseOpenAlias(qrCode);
+ }
return bcData;
}
@@ -238,4 +256,61 @@ public class BarcodeData {
return new BarcodeData(BarcodeData.Asset.BTC, address);
}
+
+ static public BarcodeData parseOpenAlias(String oaString) {
+ Timber.d("parseOpenAlias=%s", oaString);
+ if (oaString == null) return null;
+
+ Map oaAttrs = OpenAliasHelper.parse(oaString);
+ if (oaAttrs == null) return null;
+
+ String oaAsset = oaAttrs.get(OpenAliasHelper.OA1_ASSET);
+ if (oaAsset == null) return null;
+
+ String address = oaAttrs.get(OpenAliasHelper.OA1_ADDRESS);
+ if (address == null) return null;
+
+ Asset asset;
+ if (OA_XMR_ASSET.equals(oaAsset)) {
+ if (!Wallet.isAddressValid(address)) {
+ Timber.d("XMR address invalid");
+ return null;
+ }
+ asset = Asset.XMR;
+ } else if (OA_BTC_ASSET.equals(oaAsset)) {
+ if (!BitcoinAddressValidator.validate(address)) {
+ Timber.d("BTC address invalid");
+ return null;
+ }
+ asset = Asset.BTC;
+ } else {
+ Timber.i("Unsupported OpenAlias asset %s", oaAsset);
+ return null;
+ }
+
+ String paymentId = oaAttrs.get(OpenAliasHelper.OA1_PAYMENTID);
+ String description = oaAttrs.get(OpenAliasHelper.OA1_DESCRIPTION);
+ if (description == null) {
+ description = oaAttrs.get(OpenAliasHelper.OA1_NAME);
+ }
+ String amount = oaAttrs.get(OpenAliasHelper.OA1_AMOUNT);
+ String addressName = oaAttrs.get(OpenAliasHelper.OA1_NAME);
+
+ if (amount != null) {
+ try {
+ Double.parseDouble(amount);
+ } catch (NumberFormatException ex) {
+ Timber.d(ex.getLocalizedMessage());
+ return null; // we have an amount but its not a number!
+ }
+ }
+ if ((paymentId != null) && !Wallet.isPaymentIdValid(paymentId)) {
+ Timber.d("paymentId invalid");
+ return null;
+ }
+
+ BarcodeData bc = new BarcodeData(asset, address, paymentId, description, amount);
+ bc.setAddressName(addressName);
+ return bc;
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java
index 331e16a4..8b54116b 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/fragment/send/SendAddressWizardFragment.java
@@ -25,6 +25,7 @@ import android.text.Editable;
import android.text.Html;
import android.text.InputType;
import android.text.TextWatcher;
+import android.util.Patterns;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
@@ -41,8 +42,11 @@ import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.Helper;
+import com.m2049r.xmrwallet.util.OpenAliasHelper;
import com.m2049r.xmrwallet.util.UserNotes;
+import java.util.Map;
+
import timber.log.Timber;
public class SendAddressWizardFragment extends SendWizardFragment {
@@ -83,6 +87,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private TextView tvXmrTo;
private View llXmrTo;
+ private boolean resolvingOA = false;
+
OnScanListener onScanListener;
public interface OnScanListener {
@@ -108,7 +114,11 @@ public class SendAddressWizardFragment extends SendWizardFragment {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) {
- if (checkAddress()) {
+ String dnsOA = dnsFromOpenAlias(etAddress.getEditText().getText().toString());
+ Timber.d("OpenAlias is %s", dnsOA);
+ if (dnsOA != null) {
+ processOpenAlias(dnsOA);
+ } else if (checkAddress()) {
if (llPaymentId.getVisibility() == View.VISIBLE) {
etPaymentId.requestFocus();
} else {
@@ -230,6 +240,38 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return view;
}
+ private void processOpenAlias(String dnsOA) {
+ if (resolvingOA) return; // already resolving - just wait
+ if (dnsOA != null) {
+ resolvingOA = true;
+ etAddress.setError(getString(R.string.send_address_resolve_openalias));
+ OpenAliasHelper.resolve(dnsOA, new OpenAliasHelper.OnResolvedListener() {
+ @Override
+ public void onResolved(Map dataMap) {
+ resolvingOA = false;
+ BarcodeData barcodeData = dataMap.get(BarcodeData.Asset.XMR);
+ if (barcodeData == null) barcodeData = dataMap.get(BarcodeData.Asset.BTC);
+ if (barcodeData != null) {
+ Timber.d("DNSSEC=%b, %s", barcodeData.isSecure, barcodeData.address);
+ processScannedData(barcodeData);
+ etDummy.requestFocus();
+ Helper.hideKeyboard(getActivity());
+ } else {
+ etAddress.setError(getString(R.string.send_address_not_openalias));
+ Timber.d("NO XMR OPENALIAS TXT FOUND");
+ }
+ }
+
+ @Override
+ public void onFailure() {
+ resolvingOA = false;
+ etAddress.setError(getString(R.string.send_address_not_openalias));
+ Timber.e("OA FAILED");
+ }
+ });
+ } // else ignore
+ }
+
private boolean checkAddressNoError() {
String address = etAddress.getEditText().getText().toString();
return Wallet.isAddressValid(address)
@@ -276,12 +318,21 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return ok;
}
+ private void shakeAddress() {
+ etAddress.startAnimation(Helper.getShakeAnimation(getContext()));
+ }
+
@Override
public boolean onValidateFields() {
boolean ok = true;
if (!checkAddressNoError()) {
- etAddress.startAnimation(Helper.getShakeAnimation(getContext()));
+ shakeAddress();
ok = false;
+ String dnsOA = dnsFromOpenAlias(etAddress.getEditText().getText().toString());
+ Timber.d("OpenAlias is %s", dnsOA);
+ if (dnsOA != null) {
+ processOpenAlias(dnsOA);
+ }
}
if (!checkPaymentId()) {
etPaymentId.startAnimation(Helper.getShakeAnimation(getContext()));
@@ -336,7 +387,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
String scannedAddress = barcodeData.address;
if (scannedAddress != null) {
etAddress.getEditText().setText(scannedAddress);
- checkAddress();
+ if (checkAddress()) {
+ if (!barcodeData.isSecure)
+ etAddress.setError(getString(R.string.send_address_no_dnssec));
+ else
+ etAddress.setError(getString(R.string.send_address_openalias));
+ }
} else {
etAddress.getEditText().getText().clear();
etAddress.setError(null);
@@ -367,4 +423,14 @@ public class SendAddressWizardFragment extends SendWizardFragment {
Helper.hideKeyboard(getActivity());
etDummy.requestFocus();
}
+
+ String dnsFromOpenAlias(String openalias) {
+ Timber.d("checking openalias candidate %s", openalias);
+ if (Patterns.DOMAIN_NAME.matcher(openalias).matches()) return openalias;
+ if (Patterns.EMAIL_ADDRESS.matcher(openalias).matches()) {
+ openalias = openalias.replaceFirst("@", ".");
+ if (Patterns.DOMAIN_NAME.matcher(openalias).matches()) return openalias;
+ }
+ return null; // not an openalias
+ }
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/OpenAliasHelper.java b/app/src/main/java/com/m2049r/xmrwallet/util/OpenAliasHelper.java
new file mode 100644
index 00000000..13b82d30
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/OpenAliasHelper.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2018 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Specs from https://openalias.org/
+
+package com.m2049r.xmrwallet.util;
+
+import android.os.AsyncTask;
+
+import com.m2049r.xmrwallet.data.BarcodeData;
+
+import org.jitsi.dnssec.validator.ValidatingResolver;
+import org.xbill.DNS.DClass;
+import org.xbill.DNS.Flags;
+import org.xbill.DNS.Message;
+import org.xbill.DNS.Name;
+import org.xbill.DNS.RRset;
+import org.xbill.DNS.Rcode;
+import org.xbill.DNS.Record;
+import org.xbill.DNS.Section;
+import org.xbill.DNS.SimpleResolver;
+import org.xbill.DNS.TXTRecord;
+import org.xbill.DNS.Type;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import timber.log.Timber;
+
+public class OpenAliasHelper {
+ public static final String OA1_SCHEME = "oa1:";
+ public static final String OA1_ASSET = "asset";
+ public static final String OA1_ADDRESS = "recipient_address";
+ public static final String OA1_NAME = "recipient_name";
+ public static final String OA1_DESCRIPTION = "tx_description";
+ public static final String OA1_AMOUNT = "tx_amount";
+ public static final String OA1_PAYMENTID = "tx_payment_id";
+
+ public static final int DNS_LOOKUP_TIMEOUT = 2500; // ms
+
+ public static void resolve(String name, OnResolvedListener resolvedListener) {
+ new DnsTxtResolver(resolvedListener).execute(name);
+ }
+
+ public static Map parse(String oaString) {
+ return new OpenAliasParser(oaString).parse();
+ }
+
+ public interface OnResolvedListener {
+ void onResolved(Map dataMap);
+
+ void onFailure();
+ }
+
+ private static class DnsTxtResolver extends AsyncTask {
+ List txts = new ArrayList<>();
+ boolean dnssec = false;
+
+ private final OnResolvedListener resolvedListener;
+
+ private DnsTxtResolver(OnResolvedListener resolvedListener) {
+ this.resolvedListener = resolvedListener;
+ }
+
+ // trust anchor of the root zone
+ // http://data.iana.org/root-anchors/root-anchors.xml
+ final String ROOT =
+ ". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5\n" +
+ ". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D";
+ final String[] DNSSEC_SERVERS = {
+ "4.2.2.1", // Level3
+ "4.2.2.2", // Level3
+ "4.2.2.6", // Level3
+ "1.1.1.1", // cloudflare
+ "9.9.9.9", // quad9
+ "8.8.4.4", // google
+ "8.8.8.8" // google
+ };
+
+ @Override
+ protected Boolean doInBackground(String... args) {
+ //main();
+ if (args.length != 1) return false;
+ String name = args[0];
+ if ((name == null) || (name.isEmpty()))
+ return false; //pointless trying to lookup nothing
+ Timber.d("Resolving %s", name);
+ try {
+ SimpleResolver sr = new SimpleResolver(DNSSEC_SERVERS[new Random().nextInt(DNSSEC_SERVERS.length)]);
+ ValidatingResolver vr = new ValidatingResolver(sr);
+ vr.setTimeout(0, DNS_LOOKUP_TIMEOUT);
+ vr.loadTrustAnchors(new ByteArrayInputStream(ROOT.getBytes("ASCII")));
+ Record qr = Record.newRecord(Name.fromConstantString(name + "."), Type.TXT, DClass.IN);
+ Message response = vr.send(Message.newQuery(qr));
+ final int rcode = response.getRcode();
+ if (rcode != Rcode.NOERROR) {
+ Timber.i("Rcode: %s", Rcode.string(rcode));
+ for (RRset set : response.getSectionRRsets(Section.ADDITIONAL)) {
+ if (set.getName().equals(Name.root) && set.getType() == Type.TXT
+ && set.getDClass() == ValidatingResolver.VALIDATION_REASON_QCLASS) {
+ Timber.i("Reason: %s", ((TXTRecord) set.first()).getStrings().get(0));
+ }
+ }
+ return false;
+ } else {
+ dnssec = response.getHeader().getFlag(Flags.AD);
+ for (Record record : response.getSectionArray(Section.ANSWER)) {
+ if (record.getType() == Type.TXT) {
+ txts.addAll(((TXTRecord) record).getStrings());
+ }
+ }
+ }
+ } catch (IOException | IllegalArgumentException ex) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void onPostExecute(Boolean success) {
+ if (resolvedListener != null)
+ if (success) {
+ Map dataMap = new HashMap<>();
+ for (String txt : txts) {
+ BarcodeData bc = BarcodeData.parseOpenAlias(txt);
+ if (bc != null) {
+ bc.isSecure(dnssec);
+ if (!dataMap.containsKey(bc.asset)) {
+ dataMap.put(bc.asset, bc);
+ }
+ }
+ }
+ resolvedListener.onResolved(dataMap);
+ } else {
+ resolvedListener.onFailure();
+ }
+ }
+ }
+
+ private static class OpenAliasParser {
+ int currentPos = 0;
+ final String oaString;
+ StringBuilder sb = new StringBuilder();
+
+ OpenAliasParser(String oaString) {
+ this.oaString = oaString;
+ }
+
+ Map parse() {
+ if ((oaString == null) || !oaString.startsWith(OA1_SCHEME)) return null;
+ if (oaString.charAt(oaString.length() - 1) != ';') return null;
+
+ Map oaAttributes = new HashMap<>();
+
+ final int assetEnd = oaString.indexOf(' ');
+ if (assetEnd > 20) return null; // random sanity check
+ String asset = oaString.substring(OA1_SCHEME.length(), assetEnd);
+ oaAttributes.put(OA1_ASSET, asset);
+
+ boolean inQuote = false;
+ boolean inKey = true;
+ String key = null;
+ for (currentPos = assetEnd; currentPos < oaString.length() - 1; currentPos++) {
+ char c = currentChar();
+ if (inKey) {
+ if ((sb.length() == 0) && Character.isWhitespace(c)) continue;
+ if ((c == '\\') || (c == ';')) return null;
+ if (c == '=') {
+ key = sb.toString();
+ if (oaAttributes.containsKey(key)) return null; // no duplicate keys allowed
+ sb.setLength(0);
+ inKey = false;
+ } else {
+ sb.append(c);
+ }
+ continue;
+ }
+
+ // now we are in the value
+ if ((sb.length() == 0) && (c == '"')) {
+ inQuote = true;
+ continue;
+ }
+ if ((!inQuote || ((sb.length() > 0) && (c == '"'))) && (nextChar() == ';')) {
+ if (!inQuote) appendCurrentEscapedChar();
+ oaAttributes.put(key, sb.toString());
+ sb.setLength(0);
+ currentPos++; // skip the next ;
+ inQuote = false;
+ inKey = true;
+ key = null;
+ continue;
+ }
+ appendCurrentEscapedChar();
+ }
+ if (inQuote) return null;
+
+ if (key != null) {
+ oaAttributes.put(key, sb.toString());
+ }
+
+ return oaAttributes;
+ }
+
+ char currentChar() {
+ return oaString.charAt(currentPos);
+ }
+
+ char nextChar() throws IndexOutOfBoundsException {
+ int pos = currentPos;
+ char c = oaString.charAt(pos);
+ if (c == '\\') {
+ pos++;
+ }
+ return oaString.charAt(pos + 1);
+ }
+
+ void appendCurrentEscapedChar() throws IndexOutOfBoundsException {
+ char c = oaString.charAt(currentPos);
+ if (c == '\\') {
+ c = oaString.charAt(++currentPos);
+ }
+ sb.append(c);
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index af267026..818762f0 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -222,7 +222,6 @@
%1$s (Anhaltswert)
- XMR oder BTC Adresse des Empfängers
Zahlungs-ID (optional)
0.00
Private Notizen (optional)
@@ -338,6 +337,14 @@
%1$s angesteckt
%1$s abgesteckt
+ Beschreibung (optional)
+
+ OpenAlias nicht aufgelöst - Adresse nicht verfügbar
+ OpenAlias sicher ✔
+ Löse OpenAlias auf…
+ OpenAlias ohne DNSSEC - Adresse kann gefälscht sein!
+ Empfänger XMR/BTC Adresse oder OpenAlias
+
Writing Tag
Writing Tag failed!
Writing Tag successful
@@ -347,6 +354,4 @@
I don\'t know what you want!
Reading Tag successful
NFC Available!
-
- Beschreibung (optional)
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 6eee91fa..9120baa5 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -196,7 +196,6 @@
%1$s (ενδεικτικό)
- Διεύθυνση XMR ή BTC παραλήπτη
ID Πληρωμής(Payment ID)(προαιρετικό)
0.00
Προσωπικές σημειώσεις (προαιρετικό)
@@ -348,4 +347,10 @@
NFC Available!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index f377bd87..5138cd76 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -164,7 +164,6 @@
Introduce una dirección válida
Introduce tu semilla de 25 palabras
- Dirección XMR o BTC del Destinatario
ID de Pago (opcional)
0.00
Notas Privadas (opcional)
@@ -335,4 +334,10 @@
NFC Available!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index f2562361..ddf75a3f 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -224,7 +224,6 @@
%1$s (indicatif)
- Adresse XMR ou BTC du Destinataire
ID de Paiement (optionnel)
0.00
Notes Privées (optionnelles)
@@ -351,4 +350,10 @@
Use System Language
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
index 04a75cfa..eb9c1661 100644
--- a/app/src/main/res/values-hu/strings.xml
+++ b/app/src/main/res/values-hu/strings.xml
@@ -222,7 +222,6 @@
%1$s (előrejelzés)
- Kedvezményezett XMR vagy BTC címe
Fizetési azonosító (opcionális)
0.00
Privát közlemény (opcionális)
@@ -349,4 +348,10 @@
NFC elérhető!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 614ffdc0..be865350 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -223,7 +223,6 @@
%1$s (indicativo)
- Indirizzo XMR o BTC del ricevente
ID pagamento (opzionale)
0,00
Note private (opzionali)
@@ -350,4 +349,10 @@
NFC Available!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml
index 3fe6c995..91232873 100644
--- a/app/src/main/res/values-nb/strings.xml
+++ b/app/src/main/res/values-nb/strings.xml
@@ -221,7 +221,6 @@
%1$s (veiledende)
- Mottakers BTC eller XMR adresse
Betalings-ID (valgfritt)
0,00
Privat notat (valgfritt)
@@ -348,4 +347,10 @@
NFC Available!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 30519c6f..a872ba48 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -221,7 +221,6 @@
%1$s (indicativo)
- Endereço de destino XMR ou BTC
ID do pagamento (opcional)
0.00
Notas privadas (opcional)
@@ -351,4 +350,10 @@
NFC Available!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
index 242c35bf..19b73edf 100644
--- a/app/src/main/res/values-ro/strings.xml
+++ b/app/src/main/res/values-ro/strings.xml
@@ -196,7 +196,6 @@
%1$s (indicativ)
- Adresa XMR sau BTC a destinatarului
Payment ID (facultativ)
0.00
Notițe private (facultativ)
@@ -348,4 +347,10 @@
NFC Available!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index cf24590d..d9cc8326 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -223,7 +223,6 @@
%1$s (приблизительно)
- XMR или BTC адрес получателя
ID платежа (необязательно)
0.00
Персональная заметка (необязательно)
@@ -350,4 +349,10 @@
NFC Available!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
index e3c325ef..5b94759d 100644
--- a/app/src/main/res/values-sk/strings.xml
+++ b/app/src/main/res/values-sk/strings.xml
@@ -220,7 +220,6 @@
%1$s (indikatív)
- XMR alebo BTC Adresa Príjemcu
ID Platby (voliteľné)
0.00
Súkromné Poznámky (voliteľné)
@@ -347,4 +346,10 @@
NFC je dostupné!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 3a620310..4ed1c039 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -202,7 +202,6 @@
%1$s (ungefärlig)
- Mottagarens XMR- eller BTC-adress
Betalnings-ID (valfritt)
0,00
Privat anteckning (valfri)
@@ -332,4 +331,10 @@
NFC Available!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 35a79014..a163175c 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -219,7 +219,6 @@
%1$s (参考价)
- 收款者的 XMR 或 BTC 地址
付款ID (选填)
0.00
注记 (选填)
@@ -346,4 +345,10 @@
NFC Available!
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 855b4351..444397ad 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -220,7 +220,6 @@
%1$s (參考價)
- 收款者的 XMR 或 BTC 地址
付款 ID (選填)
0.00
註記 (選填)
@@ -347,4 +346,10 @@
可使用 NFC 標籤
Description (optional)
+
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
+ Receiver\'s XMR/BTC Address or OpenAlias
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 33b94174..098948cb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -228,7 +228,7 @@
%1$s (indicative)
- Receiver\'s XMR or BTC Address
+ Receiver\'s XMR/BTC Address or OpenAlias
Payment ID (optional)
0.00
Private Notes (optional)
@@ -239,6 +239,10 @@
Not a QR Code
Not a valid payment QR code
Not a valid address
+ OpenAlias not resolved - address not available
+ OpenAlias secure ✔
+ Resolving OpenAlias…
+ OpenAlias without DNSSEC - address may be spoofed
Send
Balance: %1$s XMR
Address
diff --git a/app/src/test/java/com/m2049r/xmrwallet/util/OpenAliasHelperTest.java b/app/src/test/java/com/m2049r/xmrwallet/util/OpenAliasHelperTest.java
new file mode 100644
index 00000000..2f7c5d20
--- /dev/null
+++ b/app/src/test/java/com/m2049r/xmrwallet/util/OpenAliasHelperTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2018 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.xmrwallet.util;
+
+import com.m2049r.xmrwallet.data.BarcodeData;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class OpenAliasHelperTest {
+
+ private final static String MONERUJO = "oa1:xmr recipient_address=4AdkPJoxn7JCvAby9szgnt93MSEwdnxdhaASxbTBm6x5dCwmsDep2UYN4FhStDn5i11nsJbpU7oj59ahg8gXb1Mg3viqCuk; recipient_name=Monerujo Development; tx_description=Donation to Monerujo Core Team;";
+
+ @Rule
+ public ExpectedException thrown = ExpectedException.none();
+
+ @Test
+ public void asset() {
+ Map attrs = OpenAliasHelper.parse(MONERUJO);
+ assertNotNull(attrs);
+ assertTrue(BarcodeData.OA_XMR_ASSET.equals(attrs.get(OpenAliasHelper.OA1_ASSET)));
+ }
+
+ @Test
+ public void quotedSemicolon() {
+ Map attrs = OpenAliasHelper.parse("oa1:xmr abc=\";\";def=99;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals(";"));
+ assertTrue(attrs.get("def").equals("99"));
+ }
+
+ @Test
+ public void space() {
+ Map attrs = OpenAliasHelper.parse("oa1:xmr abc=\\ ;def=99;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals(" "));
+ assertTrue(attrs.get("def").equals("99"));
+ }
+
+ @Test
+ public void quotaedSpace() {
+ Map attrs = OpenAliasHelper.parse("oa1:xmr abc=\" \";def=99;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals(" "));
+ assertTrue(attrs.get("def").equals("99"));
+ }
+
+ @Test
+ public void quotes() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=\"def\";");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals("def"));
+ }
+
+ @Test
+ public void simple() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=def;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals("def"));
+ }
+
+ @Test
+ public void duplex() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=def;ghi=jkl;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals("def"));
+ assertTrue(attrs.get("ghi").equals("jkl"));
+ }
+
+ @Test
+ public void duplexQ() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=def;ghi=jkl;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals("def"));
+ assertTrue(attrs.get("ghi").equals("jkl"));
+ }
+
+ @Test
+ public void simple_unterminated() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=def;ghi=jkl");
+ assertNull(attrs);
+ }
+
+ @Test
+ public void unterminatedQuotes() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=\"def;ghi=jkl;");
+ assertNull(attrs);
+ }
+
+ @Test
+ public void quoteEnd() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=def\";ghi=jkl;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals("def\""));
+ assertTrue(attrs.get("ghi").equals("jkl"));
+ }
+
+ @Test
+ public void quoteMiddle() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=d\"ef;ghi=jkl;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals("d\"ef"));
+ assertTrue(attrs.get("ghi").equals("jkl"));
+ }
+
+ @Test
+ public void quoteMultiple() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=d\"ef\";ghi=jkl;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals("d\"ef\""));
+ assertTrue(attrs.get("ghi").equals("jkl"));
+ }
+
+ @Test
+ public void quoteMalformedValue() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=d\"e;f\";ghi=jkl;");
+ assertNull(attrs);
+ }
+
+ @Test
+ public void quotedSemicolon2() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=\"d;ef\";ghi=jkl;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals("d;ef"));
+ assertTrue(attrs.get("ghi").equals("jkl"));
+ }
+
+ @Test
+ public void quotedQuote() {
+ Map attrs;
+ attrs = OpenAliasHelper.parse("oa1:xmr abc=\"d\"ef\";ghi=jkl;");
+ assertNotNull(attrs);
+ assertTrue(attrs.get("abc").equals("d\"ef"));
+ assertTrue(attrs.get("ghi").equals("jkl"));
+ }
+}