/*
* Created on Sep 02, 2005
*
* This file is part of susidns project, see http://susi.i2p/
*
* Copyright (C) 2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Revision: 1.1 $
*/
package i2p.susi.dns;
import java.net.IDN;
import java.util.Locale;
import java.util.Properties;
import net.i2p.I2PAppContext;
import net.i2p.crypto.SigType;
import net.i2p.data.Base32;
import net.i2p.data.Base64;
import net.i2p.data.Certificate;
public class AddressBean
{
private final String name, destination;
private Properties props;
/** available as of Java 6 */
static final boolean haveIDN;
static {
boolean h;
try {
Class.forName("java.net.IDN", false, ClassLoader.getSystemClassLoader());
h = true;
} catch (ClassNotFoundException cnfe) {
h = false;
}
haveIDN = h;
}
public AddressBean(String name, String destination)
{
this.name = name;
this.destination = destination;
}
public String getDestination()
{
return destination;
}
/**
* The ASCII (Punycode) name
*/
public String getName()
{
return name;
}
/**
* The Unicode name, translated from Punycode
* @return the original string on error
* @since 0.8.7
*/
public String getDisplayName()
{
return toUnicode(name);
}
/**
* The Unicode name, translated from Punycode
* @return the original string on error
* @since 0.8.7
*/
public static String toUnicode(String host) {
if (haveIDN)
return IDN.toUnicode(host);
return host;
}
/**
* Is the ASCII name Punycode-encoded?
* @since 0.8.7
*/
public boolean isIDN()
{
return haveIDN && !IDN.toUnicode(name).equals(name);
}
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 IllegalArgumentException on various errors or if IDN is needed but not available
* @since 0.8.7
*/
static String toASCII(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(_t("Host name \"{0}\" contains illegal character {1}",
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(_t("Host name cannot start with \"{0}\"", "-"));
if (host.startsWith("."))
throw new IllegalArgumentException(_t("Host name cannot start with \"{0}\"", "."));
if (host.endsWith("-"))
throw new IllegalArgumentException(_t("Host name cannot end with \"{0}\"", "-"));
if (host.endsWith("."))
throw new IllegalArgumentException(_t("Host name cannot end with \"{0}\"", "."));
if (needsIDN) {
if (host.startsWith("xn--"))
throw new IllegalArgumentException(_t("Host name cannot start with \"{0}\"", "xn--"));
if (host.contains(".xn--"))
throw new IllegalArgumentException(_t("Host name cannot contain \"{0}\"", ".xn--"));
if (haveIDN)
return IDN.toASCII(host, IDN.ALLOW_UNASSIGNED);
throw new IllegalArgumentException(_t("Host name \"{0}\" requires conversion to ASCII but the conversion library is unavailable in this JVM", host));
}
return host;
}
/** @since 0.8.7 */
public String getB32()
{
byte[] dest = Base64.decode(destination);
if (dest == null)
return "";
byte[] hash = I2PAppContext.getGlobalContext().sha().calculateHash(dest).getData();
return Base32.encode(hash) + ".b32.i2p";
}
/** @since 0.9 */
public String getB64()
{
byte[] dest = Base64.decode(destination);
if (dest == null)
return "";
return I2PAppContext.getGlobalContext().sha().calculateHash(dest).toBase64();
}
/** @since 0.8.7 */
public void setProperties(Properties p) {
props = p;
}
/** @since 0.8.7 */
public String getSource() {
String rv = getProp("s");
if (rv.startsWith("http://"))
rv = "<a href=\"" + rv + "\" target=\"_top\">" + rv + "</a>";
return rv;
}
/** @since 0.8.7 */
public String getAdded() {
return getDate("a");
}
/** @since 0.8.7 */
public String getModded() {
return getDate("m");
}
/** @since 0.9.26 */
public boolean isValidated() {
return Boolean.valueOf(getProp("v"));
}
/** @since 0.8.7 */
public String getNotes() {
return getProp("notes");
}
/**
* Do this the easy way
* @since 0.8.7
*/
public String getCert() {
// (4 / 3) * (pubkey length + signing key length)
String cert = destination.substring(512);
if (cert.equals("AAAA"))
return _t("None");
byte[] enc = Base64.decode(cert);
if (enc == null)
// shouldn't happen
return "invalid";
int type = enc[0] & 0xff;
switch (type) {
case Certificate.CERTIFICATE_TYPE_HASHCASH:
return _t("Hashcash");
case Certificate.CERTIFICATE_TYPE_HIDDEN:
return _t("Hidden");
case Certificate.CERTIFICATE_TYPE_SIGNED:
return _t("Signed");
case Certificate.CERTIFICATE_TYPE_KEY:
return _t("Key");
default:
return _t("Type {0}", type);
}
}
/**
* Do this the easy way
* @since 0.9.12
*/
public String getSigType() {
// (4 / 3) * (pubkey length + signing key length)
String cert = destination.substring(512);
if (cert.equals("AAAA"))
return _t("DSA 1024 bit");
byte[] enc = Base64.decode(cert);
if (enc == null)
// shouldn't happen
return "invalid";
int type = enc[0] & 0xff;
if (type != Certificate.CERTIFICATE_TYPE_KEY)
return _t("DSA 1024 bit");
int st = ((enc[3] & 0xff) << 8) | (enc[4] & 0xff);
if (st == 0)
return _t("DSA 1024 bit");
SigType stype = SigType.getByCode(st);
if (stype == null)
return _t("Type {0}", st);
return stype.toString();
}
/** @since 0.8.7 */
private String getProp(String p) {
if (props == null)
return "";
String rv = props.getProperty(p);
return rv != null ? rv : "";
}
/** @since 0.8.7 */
private String getDate(String key) {
String d = getProp(key);
if (d.length() > 0) {
try {
d = FormatDate.format(Long.parseLong(d));
} catch (NumberFormatException nfe) {}
}
return d;
}
/** translate */
private static String _t(String s) {
return Messages.getString(s);
}
/** translate */
private static String _t(String s, Object o) {
return Messages.getString(s, o);
}
/** translate */
private static String _t(String s, Object o, Object o2) {
return Messages.getString(s, o, o2);
}
}