package net.i2p.android.router.util; import android.annotation.SuppressLint; import android.content.Context; import android.content.res.Resources; import android.os.Build; import android.os.Bundle; import android.widget.Toast; import net.i2p.android.router.R; import net.i2p.android.wizard.model.Page; import net.i2p.client.naming.NamingService; import net.i2p.data.DataFormatException; import net.i2p.data.Destination; import net.i2p.router.RouterContext; import java.net.IDN; import java.util.List; import java.util.Locale; public class NamingServiceUtil { private static final String DEFAULT_NS = "BlockfileNamingService"; public static boolean addFromWizard( Context ctx, NamingService ns, Bundle data, boolean replace) { boolean success = false; // Get the Bundle keys Resources res = ctx.getResources(); String kHostName = res.getString(R.string.addressbook_add_wizard_k_name); String kDest = res.getString(R.string.addressbook_add_wizard_k_destination); String hostName = data.getBundle(kHostName).getString(Page.SIMPLE_DATA_KEY); String host = toASCII(res, hostName); // Already validated, won't throw IAE String displayHost = host.equals(hostName) ? hostName : hostName + " (" + host + ')'; String destB64 = data.getBundle(kDest).getString(Page.SIMPLE_DATA_KEY); Destination dest = new Destination(); try { dest.fromBase64(destB64); } catch (DataFormatException e) {} // Already validated // Check if already in addressbook Destination oldDest = ns.lookup(host); if (oldDest != null) { if (destB64.equals(oldDest.toBase64())) Toast.makeText(ctx, "Host name " + displayHost + " is already in address book, unchanged.", Toast.LENGTH_LONG).show(); else if (!replace) Toast.makeText(ctx, "Host name " + displayHost + " is already in address book with a different Destination.", Toast.LENGTH_LONG).show(); } else { // Put the new host name success = ns.put(host, dest); if (!success) Toast.makeText(ctx, "Failed to add Destination " + displayHost + " to naming service " + ns.getName(), Toast.LENGTH_LONG).show(); } return success; } /** @return the NamingService for the current file name, or the root NamingService */ public static NamingService getNamingService(RouterContext ctx, String book) { NamingService root = ctx.namingService(); NamingService rv = searchNamingService(root, book); return rv != null ? rv : root; } /** depth-first search */ private static NamingService searchNamingService(NamingService ns, String srch) { String name = ns.getName(); if (name.equals(srch) || basename(name).equals(srch) || name.equals(DEFAULT_NS)) return ns; List<NamingService> list = ns.getNamingServices(); if (list != null) { for (NamingService nss : list) { NamingService rv = searchNamingService(nss, srch); if (rv != null) return rv; } } return null; } private static String basename(String filename) { int slash = filename.lastIndexOf('/'); if (slash >= 0) filename = filename.substring(slash + 1); return filename; } private static final char DOT = '.'; private static final char DOT2 = 0x3002; private static final char DOT3 = 0xFF0E; private static final char DOT4 = 0xFF61; /** * Ref: java.net.IDN and RFC 3940 * @param host will be converted to lower case * @return name converted to lower case and punycoded if necessary * @throws java.lang.IllegalArgumentException on various errors or if IDN is needed but not available * @since 0.8.7 */ @SuppressLint("NewApi") static String toASCII(Resources res, String host) throws IllegalArgumentException { host = host.toLowerCase(Locale.US); boolean needsIDN = false; // Here we do easy checks and throw translated exceptions. // We do checks on the whole host name, not on each "label", so // we allow '.', and some untranslated errors will be thrown by IDN.toASCII() for (int i = 0; i < host.length(); i++) { char c = host.charAt(i); if (c <= 0x2c || c == 0x2f || c >= 0x3a && c <= 0x40 || c >= 0x5b && c <= 0x60 || c >= 0x7b && c <= 0x7f) { String bad = "\"" + c + "\" (0x" + Integer.toHexString(c) + ')'; throw new IllegalArgumentException( res.getString(R.string.nsu_iae_illegal_char, host, bad)); } if (c == DOT2) host = host.replace(DOT2, DOT); else if (c == DOT3) host = host.replace(DOT3, DOT); else if (c == DOT4) host = host.replace(DOT4, DOT); else if (c > 0x7f) needsIDN = true; } if (host.startsWith("-")) throw new IllegalArgumentException( res.getString(R.string.nsu_iae_cannot_start_with, "-")); if (host.startsWith(".")) throw new IllegalArgumentException( res.getString(R.string.nsu_iae_cannot_start_with, ".")); if (host.endsWith("-")) throw new IllegalArgumentException( res.getString(R.string.nsu_iae_cannot_end_with, "-")); if (host.endsWith(".")) throw new IllegalArgumentException( res.getString(R.string.nsu_iae_cannot_end_with, ".")); if (needsIDN) { if (host.startsWith("xn--")) throw new IllegalArgumentException( res.getString(R.string.nsu_iae_cannot_start_with, "xn--")); if (host.contains(".xn--")) throw new IllegalArgumentException( res.getString(R.string.nsu_iae_cannot_contain, ".xn--")); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) return IDN.toASCII(host, IDN.ALLOW_UNASSIGNED); else throw new IllegalArgumentException( res.getString(R.string.nsu_iae_requires_conversion, host)); } return host; } }