OpenAlias support for XMR & BTC (#404)

* with support for OpenAlias QR Codes
This commit is contained in:
m2049r 2018-09-17 09:03:07 +02:00 committed by GitHub
parent 8b28e3ea1e
commit 9f9bc4793d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 657 additions and 23 deletions

View File

@ -82,6 +82,11 @@ dependencies {
implementation 'com.nulab-inc:zxcvbn:1.2.3' 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 "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion" testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"

View File

@ -3,6 +3,7 @@
package="com.m2049r.xmrwallet"> package="com.m2049r.xmrwallet">
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />

View File

@ -273,6 +273,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
break; break;
case Toolbar.BUTTON_CANCEL: case Toolbar.BUTTON_CANCEL:
onDisposeRequest(); onDisposeRequest();
Helper.hideKeyboard(WalletActivity.this);
WalletActivity.super.onBackPressed(); WalletActivity.super.onBackPressed();
break; break;
case Toolbar.BUTTON_CLOSE: case Toolbar.BUTTON_CLOSE:
@ -920,7 +921,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
drawer.closeDrawer(GravityCompat.START); drawer.closeDrawer(GravityCompat.START);
return; return;
} }
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container); final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment instanceof OnBackPressedListener) { if (fragment instanceof OnBackPressedListener) {
if (!((OnBackPressedListener) fragment).onBackPressed()) { if (!((OnBackPressedListener) fragment).onBackPressed()) {

View File

@ -20,6 +20,7 @@ import android.net.Uri;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.OpenAliasHelper;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -32,6 +33,9 @@ public class BarcodeData {
public static final String XMR_AMOUNT = "tx_amount"; public static final String XMR_AMOUNT = "tx_amount";
public static final String XMR_DESCRIPTION = "tx_description"; 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_SCHEME = "bitcoin:";
static final String BTC_AMOUNT = "amount"; static final String BTC_AMOUNT = "amount";
@ -40,10 +44,12 @@ public class BarcodeData {
} }
public Asset asset = null; public Asset asset = null;
public String addressName = null;
public String address = null; public String address = null;
public String paymentId = null; public String paymentId = null;
public String amount = null; public String amount = null;
public String description = null; public String description = null;
public boolean isSecure = true;
public BarcodeData(String uri) { public BarcodeData(String uri) {
this.asset = asset; this.asset = asset;
@ -76,6 +82,14 @@ public class BarcodeData {
this.amount = amount; this.amount = amount;
} }
public void setAddressName(String name) {
addressName = name;
}
public void isSecure(boolean isSecure) {
this.isSecure = isSecure;
}
public Uri getUri() { public Uri getUri() {
return Uri.parse(getUriString()); return Uri.parse(getUriString());
} }
@ -95,7 +109,7 @@ public class BarcodeData {
first = false; first = false;
sb.append(BarcodeData.XMR_DESCRIPTION).append('=').append(Uri.encode(description)); sb.append(BarcodeData.XMR_DESCRIPTION).append('=').append(Uri.encode(description));
} }
if (!amount.isEmpty()) { if ((amount != null) && !amount.isEmpty()) {
sb.append(first ? "?" : "&"); sb.append(first ? "?" : "&");
sb.append(BarcodeData.XMR_AMOUNT).append('=').append(amount); sb.append(BarcodeData.XMR_AMOUNT).append('=').append(amount);
} }
@ -113,10 +127,14 @@ public class BarcodeData {
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinUri(qrCode); bcData = parseBitcoinUri(qrCode);
} }
// check for naked btc addres // check for naked btc address
if (bcData == null) { if (bcData == null) {
bcData = parseBitcoinNaked(qrCode); bcData = parseBitcoinNaked(qrCode);
} }
// check for OpenAlias
if (bcData == null) {
bcData = parseOpenAlias(qrCode);
}
return bcData; return bcData;
} }
@ -238,4 +256,61 @@ public class BarcodeData {
return new BarcodeData(BarcodeData.Asset.BTC, address); return new BarcodeData(BarcodeData.Asset.BTC, address);
} }
static public BarcodeData parseOpenAlias(String oaString) {
Timber.d("parseOpenAlias=%s", oaString);
if (oaString == null) return null;
Map<String, String> 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;
}
} }

View File

@ -25,6 +25,7 @@ import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Patterns;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -41,8 +42,11 @@ import com.m2049r.xmrwallet.data.TxDataBtc;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.BitcoinAddressValidator; import com.m2049r.xmrwallet.util.BitcoinAddressValidator;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OpenAliasHelper;
import com.m2049r.xmrwallet.util.UserNotes; import com.m2049r.xmrwallet.util.UserNotes;
import java.util.Map;
import timber.log.Timber; import timber.log.Timber;
public class SendAddressWizardFragment extends SendWizardFragment { public class SendAddressWizardFragment extends SendWizardFragment {
@ -83,6 +87,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private TextView tvXmrTo; private TextView tvXmrTo;
private View llXmrTo; private View llXmrTo;
private boolean resolvingOA = false;
OnScanListener onScanListener; OnScanListener onScanListener;
public interface OnScanListener { public interface OnScanListener {
@ -108,7 +114,11 @@ public class SendAddressWizardFragment extends SendWizardFragment {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_NEXT)) { || (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) { if (llPaymentId.getVisibility() == View.VISIBLE) {
etPaymentId.requestFocus(); etPaymentId.requestFocus();
} else { } else {
@ -230,6 +240,38 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return view; 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<BarcodeData.Asset, BarcodeData> 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() { private boolean checkAddressNoError() {
String address = etAddress.getEditText().getText().toString(); String address = etAddress.getEditText().getText().toString();
return Wallet.isAddressValid(address) return Wallet.isAddressValid(address)
@ -276,12 +318,21 @@ public class SendAddressWizardFragment extends SendWizardFragment {
return ok; return ok;
} }
private void shakeAddress() {
etAddress.startAnimation(Helper.getShakeAnimation(getContext()));
}
@Override @Override
public boolean onValidateFields() { public boolean onValidateFields() {
boolean ok = true; boolean ok = true;
if (!checkAddressNoError()) { if (!checkAddressNoError()) {
etAddress.startAnimation(Helper.getShakeAnimation(getContext())); shakeAddress();
ok = false; ok = false;
String dnsOA = dnsFromOpenAlias(etAddress.getEditText().getText().toString());
Timber.d("OpenAlias is %s", dnsOA);
if (dnsOA != null) {
processOpenAlias(dnsOA);
}
} }
if (!checkPaymentId()) { if (!checkPaymentId()) {
etPaymentId.startAnimation(Helper.getShakeAnimation(getContext())); etPaymentId.startAnimation(Helper.getShakeAnimation(getContext()));
@ -336,7 +387,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
String scannedAddress = barcodeData.address; String scannedAddress = barcodeData.address;
if (scannedAddress != null) { if (scannedAddress != null) {
etAddress.getEditText().setText(scannedAddress); 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 { } else {
etAddress.getEditText().getText().clear(); etAddress.getEditText().getText().clear();
etAddress.setError(null); etAddress.setError(null);
@ -367,4 +423,14 @@ public class SendAddressWizardFragment extends SendWizardFragment {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
etDummy.requestFocus(); 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
}
} }

View File

@ -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<String, String> parse(String oaString) {
return new OpenAliasParser(oaString).parse();
}
public interface OnResolvedListener {
void onResolved(Map<BarcodeData.Asset, BarcodeData> dataMap);
void onFailure();
}
private static class DnsTxtResolver extends AsyncTask<String, Void, Boolean> {
List<String> 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<BarcodeData.Asset, BarcodeData> 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<String, String> parse() {
if ((oaString == null) || !oaString.startsWith(OA1_SCHEME)) return null;
if (oaString.charAt(oaString.length() - 1) != ';') return null;
Map<String, String> 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);
}
}
}

View File

@ -222,7 +222,6 @@
<string name="send_amount_btc_xmr">%1$s (Anhaltswert)</string> <string name="send_amount_btc_xmr">%1$s (Anhaltswert)</string>
<string name="send_address_hint">XMR oder BTC Adresse des Empfängers</string>
<string name="send_paymentid_hint">Zahlungs-ID (optional)</string> <string name="send_paymentid_hint">Zahlungs-ID (optional)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Private Notizen (optional)</string> <string name="send_notes_hint">Private Notizen (optional)</string>
@ -338,6 +337,14 @@
<string name="toast_ledger_attached">%1$s angesteckt</string> <string name="toast_ledger_attached">%1$s angesteckt</string>
<string name="toast_ledger_detached">%1$s abgesteckt</string> <string name="toast_ledger_detached">%1$s abgesteckt</string>
<string name="receive_desc_hint">Beschreibung (optional)</string>
<string name="send_address_not_openalias">OpenAlias nicht aufgelöst - Adresse nicht verfügbar</string>
<string name="send_address_openalias">OpenAlias sicher &#x2714;</string>
<string name="send_address_resolve_openalias">Löse OpenAlias auf&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias ohne DNSSEC - Adresse kann gefälscht sein!</string>
<string name="send_address_hint">Empfänger XMR/BTC Adresse oder OpenAlias</string>
<string name="progress_nfc_write">Writing Tag</string> <string name="progress_nfc_write">Writing Tag</string>
<string name="nfc_write_failed">Writing Tag failed!</string> <string name="nfc_write_failed">Writing Tag failed!</string>
<string name="nfc_write_successful">Writing Tag successful</string> <string name="nfc_write_successful">Writing Tag successful</string>
@ -347,6 +354,4 @@
<string name="nfc_tag_read_what">I don\'t know what you want!</string> <string name="nfc_tag_read_what">I don\'t know what you want!</string>
<string name="nfc_tag_read_success">Reading Tag successful</string> <string name="nfc_tag_read_success">Reading Tag successful</string>
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Beschreibung (optional)</string>
</resources> </resources>

View File

@ -196,7 +196,6 @@
<string name="send_amount_btc_xmr">%1$s (ενδεικτικό)</string> <string name="send_amount_btc_xmr">%1$s (ενδεικτικό)</string>
<string name="send_address_hint">Διεύθυνση XMR ή BTC παραλήπτη</string>
<string name="send_paymentid_hint">ID Πληρωμής(Payment ID)(προαιρετικό)</string> <string name="send_paymentid_hint">ID Πληρωμής(Payment ID)(προαιρετικό)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Προσωπικές σημειώσεις (προαιρετικό)</string> <string name="send_notes_hint">Προσωπικές σημειώσεις (προαιρετικό)</string>
@ -348,4 +347,10 @@
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -164,7 +164,6 @@
<string name="generate_check_address">Introduce una dirección válida</string> <string name="generate_check_address">Introduce una dirección válida</string>
<string name="generate_check_mnemonic">Introduce tu semilla de 25 palabras</string> <string name="generate_check_mnemonic">Introduce tu semilla de 25 palabras</string>
<string name="send_address_hint">Dirección XMR o BTC del Destinatario</string>
<string name="send_paymentid_hint">ID de Pago (opcional)</string> <string name="send_paymentid_hint">ID de Pago (opcional)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Notas Privadas (opcional)</string> <string name="send_notes_hint">Notas Privadas (opcional)</string>
@ -335,4 +334,10 @@
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -224,7 +224,6 @@
<string name="send_amount_btc_xmr">%1$s (indicatif)</string> <string name="send_amount_btc_xmr">%1$s (indicatif)</string>
<string name="send_address_hint">Adresse XMR ou BTC du Destinataire</string>
<string name="send_paymentid_hint">ID de Paiement (optionnel)</string> <string name="send_paymentid_hint">ID de Paiement (optionnel)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Notes Privées (optionnelles)</string> <string name="send_notes_hint">Notes Privées (optionnelles)</string>
@ -351,4 +350,10 @@
<string name="language_system_default">Use System Language</string> <string name="language_system_default">Use System Language</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -222,7 +222,6 @@
<string name="send_amount_btc_xmr">%1$s (előrejelzés)</string> <string name="send_amount_btc_xmr">%1$s (előrejelzés)</string>
<string name="send_address_hint">Kedvezményezett XMR vagy BTC címe</string>
<string name="send_paymentid_hint">Fizetési azonosító (opcionális)</string> <string name="send_paymentid_hint">Fizetési azonosító (opcionális)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Privát közlemény (opcionális)</string> <string name="send_notes_hint">Privát közlemény (opcionális)</string>
@ -349,4 +348,10 @@
<string name="nfc_tag_tap">NFC elérhető!</string> <string name="nfc_tag_tap">NFC elérhető!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -223,7 +223,6 @@
<string name="send_amount_btc_xmr">%1$s (indicativo)</string> <string name="send_amount_btc_xmr">%1$s (indicativo)</string>
<string name="send_address_hint">Indirizzo XMR o BTC del ricevente</string>
<string name="send_paymentid_hint">ID pagamento (opzionale)</string> <string name="send_paymentid_hint">ID pagamento (opzionale)</string>
<string name="send_amount_hint">0,00</string> <string name="send_amount_hint">0,00</string>
<string name="send_notes_hint">Note private (opzionali)</string> <string name="send_notes_hint">Note private (opzionali)</string>
@ -350,4 +349,10 @@
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -221,7 +221,6 @@
<string name="send_amount_btc_xmr">%1$s (veiledende)</string> <string name="send_amount_btc_xmr">%1$s (veiledende)</string>
<string name="send_address_hint">Mottakers BTC eller XMR adresse</string>
<string name="send_paymentid_hint">Betalings-ID (valgfritt)</string> <string name="send_paymentid_hint">Betalings-ID (valgfritt)</string>
<string name="send_amount_hint">0,00</string> <string name="send_amount_hint">0,00</string>
<string name="send_notes_hint">Privat notat (valgfritt)</string> <string name="send_notes_hint">Privat notat (valgfritt)</string>
@ -348,4 +347,10 @@
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -221,7 +221,6 @@
<string name="send_amount_btc_xmr">%1$s (indicativo)</string> <string name="send_amount_btc_xmr">%1$s (indicativo)</string>
<string name="send_address_hint">Endereço de destino XMR ou BTC</string>
<string name="send_paymentid_hint">ID do pagamento (opcional)</string> <string name="send_paymentid_hint">ID do pagamento (opcional)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Notas privadas (opcional)</string> <string name="send_notes_hint">Notas privadas (opcional)</string>
@ -351,4 +350,10 @@
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -196,7 +196,6 @@
<string name="send_amount_btc_xmr">%1$s (indicativ)</string> <string name="send_amount_btc_xmr">%1$s (indicativ)</string>
<string name="send_address_hint">Adresa XMR sau BTC a destinatarului</string>
<string name="send_paymentid_hint">Payment ID (facultativ)</string> <string name="send_paymentid_hint">Payment ID (facultativ)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Notițe private (facultativ)</string> <string name="send_notes_hint">Notițe private (facultativ)</string>
@ -348,4 +347,10 @@
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -223,7 +223,6 @@
<string name="send_amount_btc_xmr">%1$s (приблизительно)</string> <string name="send_amount_btc_xmr">%1$s (приблизительно)</string>
<string name="send_address_hint">XMR или BTC адрес получателя</string>
<string name="send_paymentid_hint">ID платежа (необязательно)</string> <string name="send_paymentid_hint">ID платежа (необязательно)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Персональная заметка (необязательно)</string> <string name="send_notes_hint">Персональная заметка (необязательно)</string>
@ -350,4 +349,10 @@
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -220,7 +220,6 @@
<string name="send_amount_btc_xmr">%1$s (indikatív)</string> <string name="send_amount_btc_xmr">%1$s (indikatív)</string>
<string name="send_address_hint">XMR alebo BTC Adresa Príjemcu</string>
<string name="send_paymentid_hint">ID Platby (voliteľné)</string> <string name="send_paymentid_hint">ID Platby (voliteľné)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Súkromné Poznámky (voliteľné)</string> <string name="send_notes_hint">Súkromné Poznámky (voliteľné)</string>
@ -347,4 +346,10 @@
<string name="nfc_tag_tap">NFC je dostupné!</string> <string name="nfc_tag_tap">NFC je dostupné!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -202,7 +202,6 @@
<string name="send_amount_btc_xmr">%1$s (ungefärlig)</string> <string name="send_amount_btc_xmr">%1$s (ungefärlig)</string>
<string name="send_address_hint">Mottagarens XMR- eller BTC-adress</string>
<string name="send_paymentid_hint">Betalnings-ID (valfritt)</string> <string name="send_paymentid_hint">Betalnings-ID (valfritt)</string>
<string name="send_amount_hint">0,00</string> <string name="send_amount_hint">0,00</string>
<string name="send_notes_hint">Privat anteckning (valfri)</string> <string name="send_notes_hint">Privat anteckning (valfri)</string>
@ -332,4 +331,10 @@
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -219,7 +219,6 @@
<string name="send_amount_btc_xmr">%1$s (参考价)</string> <string name="send_amount_btc_xmr">%1$s (参考价)</string>
<string name="send_address_hint">收款者的 XMR 或 BTC 地址</string>
<string name="send_paymentid_hint">付款ID (选填)</string> <string name="send_paymentid_hint">付款ID (选填)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">注记 (选填)</string> <string name="send_notes_hint">注记 (选填)</string>
@ -346,4 +345,10 @@
<string name="nfc_tag_tap">NFC Available!</string> <string name="nfc_tag_tap">NFC Available!</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -220,7 +220,6 @@
<string name="send_amount_btc_xmr">%1$s (參考價)</string> <string name="send_amount_btc_xmr">%1$s (參考價)</string>
<string name="send_address_hint">收款者的 XMR 或 BTC 地址</string>
<string name="send_paymentid_hint">付款 ID (選填)</string> <string name="send_paymentid_hint">付款 ID (選填)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">註記 (選填)</string> <string name="send_notes_hint">註記 (選填)</string>
@ -347,4 +346,10 @@
<string name="nfc_tag_tap">可使用 NFC 標籤</string> <string name="nfc_tag_tap">可使用 NFC 標籤</string>
<string name="receive_desc_hint">Description (optional)</string> <string name="receive_desc_hint">Description (optional)</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
</resources> </resources>

View File

@ -228,7 +228,7 @@
<string name="send_amount_btc_xmr">%1$s (indicative)</string> <string name="send_amount_btc_xmr">%1$s (indicative)</string>
<string name="send_address_hint">Receiver\'s XMR or BTC Address</string> <string name="send_address_hint">Receiver\'s XMR/BTC Address or OpenAlias</string>
<string name="send_paymentid_hint">Payment ID (optional)</string> <string name="send_paymentid_hint">Payment ID (optional)</string>
<string name="send_amount_hint">0.00</string> <string name="send_amount_hint">0.00</string>
<string name="send_notes_hint">Private Notes (optional)</string> <string name="send_notes_hint">Private Notes (optional)</string>
@ -239,6 +239,10 @@
<string name="send_qr_invalid">Not a QR Code</string> <string name="send_qr_invalid">Not a QR Code</string>
<string name="send_qr_address_invalid">Not a valid payment QR code</string> <string name="send_qr_address_invalid">Not a valid payment QR code</string>
<string name="send_address_invalid">Not a valid address</string> <string name="send_address_invalid">Not a valid address</string>
<string name="send_address_not_openalias">OpenAlias not resolved - address not available</string>
<string name="send_address_openalias">OpenAlias secure &#x2714;</string>
<string name="send_address_resolve_openalias">Resolving OpenAlias&#8230;</string>
<string name="send_address_no_dnssec">OpenAlias without DNSSEC - address may be spoofed</string>
<string name="send_title">Send</string> <string name="send_title">Send</string>
<string name="send_available">Balance: %1$s XMR</string> <string name="send_available">Balance: %1$s XMR</string>
<string name="send_address_title">Address</string> <string name="send_address_title">Address</string>

View File

@ -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<String, String> attrs = OpenAliasHelper.parse(MONERUJO);
assertNotNull(attrs);
assertTrue(BarcodeData.OA_XMR_ASSET.equals(attrs.get(OpenAliasHelper.OA1_ASSET)));
}
@Test
public void quotedSemicolon() {
Map<String, String> 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<String, String> 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<String, String> 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<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=\"def\";");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("def"));
}
@Test
public void simple() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=def;");
assertNotNull(attrs);
assertTrue(attrs.get("abc").equals("def"));
}
@Test
public void duplex() {
Map<String, String> 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<String, String> 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<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=def;ghi=jkl");
assertNull(attrs);
}
@Test
public void unterminatedQuotes() {
Map<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=\"def;ghi=jkl;");
assertNull(attrs);
}
@Test
public void quoteEnd() {
Map<String, String> 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<String, String> 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<String, String> 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<String, String> attrs;
attrs = OpenAliasHelper.parse("oa1:xmr abc=d\"e;f\";ghi=jkl;");
assertNull(attrs);
}
@Test
public void quotedSemicolon2() {
Map<String, String> 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<String, String> 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"));
}
}