summaryrefslogtreecommitdiff
path: root/libjava/classpath/gnu/java/security/der
diff options
context:
space:
mode:
Diffstat (limited to 'libjava/classpath/gnu/java/security/der')
-rw-r--r--libjava/classpath/gnu/java/security/der/BitString.java332
-rw-r--r--libjava/classpath/gnu/java/security/der/DER.java86
-rw-r--r--libjava/classpath/gnu/java/security/der/DEREncodingException.java54
-rw-r--r--libjava/classpath/gnu/java/security/der/DERReader.java439
-rw-r--r--libjava/classpath/gnu/java/security/der/DERValue.java189
-rw-r--r--libjava/classpath/gnu/java/security/der/DERWriter.java355
-rw-r--r--libjava/classpath/gnu/java/security/der/package.html46
7 files changed, 1501 insertions, 0 deletions
diff --git a/libjava/classpath/gnu/java/security/der/BitString.java b/libjava/classpath/gnu/java/security/der/BitString.java
new file mode 100644
index 000000000..ac10be22e
--- /dev/null
+++ b/libjava/classpath/gnu/java/security/der/BitString.java
@@ -0,0 +1,332 @@
+/* BitString.java -- Java representation of the BIT STRING type.
+ Copyright (C) 2003 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.security.der;
+
+import gnu.java.lang.CPStringBuilder;
+
+import java.math.BigInteger;
+import java.util.Arrays;
+
+/**
+ * Immutable representation of a bit string, which is equivalent to a
+ * byte array except some number of the rightmost bits are ignored. For
+ * example, this could be the bit string:
+ *
+ * <pre> 00010101 11101101 11010xxx</pre>
+ *
+ * <p>Where the "xxx" represents three bits that should be ignored, and
+ * can have any value.
+ *
+ * @author Casey Marshall (csm@gnu.org)
+ */
+public class BitString implements Cloneable, Comparable
+{
+
+ // Fields.
+ // ------------------------------------------------------------------------
+
+ /** The bits themselves. */
+ private final byte[] bytes;
+
+ /**
+ * The exportable byte array. This array has the ignored bits
+ * removed.
+ */
+ private transient byte[] externBytes;
+
+ /** The number of bits ignored at the end of the byte array. */
+ private final int ignoredBits;
+
+ /** This bit string as a boolean array. */
+ private transient boolean[] boolVal;
+
+ // Constructors.
+ // ------------------------------------------------------------------------
+
+ /**
+ * Create a new bit string, shifting the given byte array if needed.
+ *
+ * @param bytes The byte array holding the bit string.
+ * @param ignoredBits The number of bits to ignore.
+ * @param doShift Pass true in this parameter if the byte array has
+ * not yet been shifted left by <i>ignoredBits</i>.
+ * @throws IllegalArgumentException If <i>ignoredBits</i> is negative
+ * or greater than 7.
+ * @throws NullPointerException If <i>bytes</i> is null.
+ */
+ public BitString(byte[] bytes, int ignoredBits, boolean doShift)
+ {
+ this(bytes, 0, bytes.length, ignoredBits, doShift);
+ }
+
+ /**
+ * Create a new bit string, shifting the given byte array if needed.
+ *
+ * @param bytes The byte array holding the bit string.
+ * @param offset The offset where the meaningful bytes begin.
+ * @param length The number of meaningful bytes.
+ * @param ignoredBits The number of bits to ignore.
+ * @param doShift Pass true in this parameter if the byte array has
+ * not yet been shifted left by <i>ignoredBits</i>.
+ * @throws IllegalArgumentException If <i>ignoredBits</i> is negative
+ * or greater than 7.
+ * @throws NullPointerException If <i>bytes</i> is null.
+ */
+ public BitString(byte[] bytes, int offset, int length,
+ int ignoredBits, boolean doShift)
+ {
+ if (ignoredBits < 0 || ignoredBits > 7)
+ throw new IllegalArgumentException();
+ if (bytes == null)
+ throw new NullPointerException();
+ if (doShift && ignoredBits > 0)
+ {
+ this.externBytes = new byte[length];
+ System.arraycopy(bytes, offset, externBytes, 0, length);
+ this.bytes = new BigInteger(externBytes).shiftLeft(ignoredBits)
+ .toByteArray();
+ }
+ else
+ {
+ this.bytes = new byte[length];
+ System.arraycopy(bytes, offset, this.bytes, 0, length);
+ }
+ this.ignoredBits = ignoredBits;
+ }
+
+ /**
+ * Create a new bit string.
+ *
+ * @param bytes The byte array holding the bit string.
+ * @param offset The offset where the meaningful bytes begin.
+ * @param length The number of meaningful bytes.
+ * @param ignoredBits The number of bits to ignore.
+ * @throws IllegalArgumentException If <i>ignoredBits</i> is negative
+ * or greater than 7.
+ * @throws NullPointerException If <i>bytes</i> is null.
+ */
+ public BitString(byte[] bytes, int offset, int length, int ignoredBits)
+ {
+ this(bytes, offset, length, ignoredBits, false);
+ }
+
+ /**
+ * Create a new bit string.
+ *
+ * @param bytes The byte array holding the bit string.
+ * @param ignoredBits The number of bits to ignore.
+ * @throws IllegalArgumentException If <i>ignoredBits</i> is negative
+ * or greater than 7.
+ * @throws NullPointerException If <i>bytes</i> is null.
+ */
+ public BitString(byte[] bytes, int ignoredBits)
+ {
+ this(bytes, 0, bytes.length, ignoredBits, false);
+ }
+
+ /**
+ * Create a new bit string.
+ *
+ * @param bytes The byte array holding the bit string.
+ * @param offset The offset where the meaningful bytes begin.
+ * @param length The number of meaningful bytes.
+ * @throws NullPointerException If <i>bytes</i> is null.
+ */
+ public BitString(byte[] bytes, int offset, int length)
+ {
+ this(bytes, offset, length, 0, false);
+ }
+
+ /**
+ * Create a new bit string.
+ *
+ * @param bytes The byte array holding the bit string.
+ * @throws NullPointerException If <i>bytes</i> is null.
+ */
+ public BitString(byte[] bytes)
+ {
+ this(bytes, 0, bytes.length, 0, false);
+ }
+
+ // Instance methods.
+ // ------------------------------------------------------------------------
+
+ /**
+ * Return this bit string as a byte array, with the ignored bits
+ * trimmed off. The byte array is cloned every time this method is
+ * called to prevent modification.
+ *
+ * @return The trimmed byte array.
+ */
+ public byte[] toByteArray()
+ {
+ if (ignoredBits == 0)
+ return (byte[]) bytes.clone();
+ if (externBytes == null)
+ externBytes = new BigInteger(bytes).shiftRight(ignoredBits).toByteArray();
+ return (byte[]) externBytes.clone();
+ }
+
+ /**
+ * Returns this bit string as a byte array, with the ignored bits
+ * present. The byte array is cloned every time this method is
+ * called to prevent modification.
+ *
+ * @return The byte array.
+ */
+ public byte[] getShiftedByteArray()
+ {
+ return (byte[]) bytes.clone();
+ }
+
+ /**
+ * Returns the number of ignored bits.
+ *
+ * @return The number of ignored bits.
+ */
+ public int getIgnoredBits()
+ {
+ return ignoredBits;
+ }
+
+ /**
+ * Returns the size, in bits, of this bit string.
+ *
+ * @return The size of this bit string.
+ */
+ public int size()
+ {
+ return (bytes.length << 3) - ignoredBits;
+ }
+
+ /**
+ * Return this bit string as a boolean array. The value returned is of
+ * size {@link #size()}, and each <code>true</code> value
+ * corresponding to each "1" in this bit string. The boolean array is
+ * cloned before it is returned.
+ *
+ * @return The boolean array.
+ */
+ public boolean[] toBooleanArray()
+ {
+ if (boolVal == null)
+ {
+ boolVal = new boolean[size()];
+ for (int i = 0, j = 7, k = 0; i < boolVal.length; i++)
+ {
+ boolVal[i] = (bytes[k] & 1 << j--) != 0;
+ if (j < 0)
+ {
+ j = 7;
+ k++;
+ }
+ }
+ }
+ return (boolean[]) boolVal.clone();
+ }
+
+ public Object clone()
+ {
+ try
+ {
+ return super.clone();
+ }
+ catch (CloneNotSupportedException cce)
+ {
+ throw new InternalError(cce.getMessage());
+ }
+ }
+
+ public int compareTo(Object o)
+ {
+ BitString that = (BitString) o;
+ if (this.equals(that))
+ return 0;
+ if (this.bytes.length != that.bytes.length)
+ return (this.bytes.length < that.bytes.length) ? -1 : 1;
+ if (this.ignoredBits != that.ignoredBits)
+ return (this.ignoredBits < that.ignoredBits) ? -1 : 1;
+ for (int i = 0; i < this.bytes.length; i++)
+ if (this.bytes[i] != that.bytes[i])
+ return (this.bytes[i] < that.bytes[i]) ? -1 : 1;
+ return 0; // not reached.
+ }
+
+ public int hashCode()
+ {
+ int result = 0;
+ for (int i = 0; i < bytes.length - 1; ++i)
+ result = result * 31 + bytes[i];
+ if (bytes.length > 0)
+ {
+ int lastByte = bytes[bytes.length - 1] & ~ ((1 << ignoredBits) - 1);
+ result = result * 31 + lastByte;
+ }
+ return result;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (!(o instanceof BitString))
+ return false;
+ BitString that = (BitString) o;
+ // True for cloned instances.
+ if (this.bytes == that.bytes && this.ignoredBits == that.ignoredBits)
+ return true;
+ if (this.ignoredBits == that.ignoredBits)
+ return Arrays.equals(this.bytes, that.bytes);
+ return false;
+ }
+
+ public String toString()
+ {
+ CPStringBuilder sb = new CPStringBuilder();
+ for (int i = 0, j = 7, k = 0; i < size(); i++)
+ {
+ sb.append((bytes[k] & 1 << j) != 0 ? "1" : "0");
+ j--;
+ if (j < 0)
+ {
+ j = 7;
+ k++;
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/libjava/classpath/gnu/java/security/der/DER.java b/libjava/classpath/gnu/java/security/der/DER.java
new file mode 100644
index 000000000..a7eb4a689
--- /dev/null
+++ b/libjava/classpath/gnu/java/security/der/DER.java
@@ -0,0 +1,86 @@
+/* DER.java -- Basic constants in DER sequences.
+ Copyright (C) 2003 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.security.der;
+
+/**
+ * The set of tags for DER types.
+ *
+ * @author Casey Marshall (csm@gnu.org)
+ */
+public interface DER
+{
+ int UNIVERSAL = 0x00;
+ int APPLICATION = 0x40;
+ int CONTEXT = 0x80;
+ int PRIVATE = 0xC0;
+
+ int CONSTRUCTED = 0x20;
+
+ int ANY = 0x00;
+ int BOOLEAN = 0x01;
+ int INTEGER = 0x02;
+ int BIT_STRING = 0x03;
+ int OCTET_STRING = 0x04;
+ int NULL = 0x05;
+ int OBJECT_IDENTIFIER = 0x06;
+ int REAL = 0x09;
+ int ENUMERATED = 0x0a;
+ int RELATIVE_OID = 0x0d;
+
+ int SEQUENCE = 0x10;
+ int SET = 0x11;
+
+ Object CONSTRUCTED_VALUE = new Object();
+
+ int NUMERIC_STRING = 0x12;
+ int PRINTABLE_STRING = 0x13;
+ int T61_STRING = 0x14;
+ int VIDEOTEX_STRING = 0x15;
+ int IA5_STRING = 0x16;
+ int GRAPHIC_STRING = 0x19;
+ int ISO646_STRING = 0x1A;
+ int GENERAL_STRING = 0x1B;
+
+ int UTF8_STRING = 0x0C;
+ int UNIVERSAL_STRING = 0x1C;
+ int BMP_STRING = 0x1E;
+
+ int UTC_TIME = 0x17;
+ int GENERALIZED_TIME = 0x18;
+}
diff --git a/libjava/classpath/gnu/java/security/der/DEREncodingException.java b/libjava/classpath/gnu/java/security/der/DEREncodingException.java
new file mode 100644
index 000000000..90042a3fc
--- /dev/null
+++ b/libjava/classpath/gnu/java/security/der/DEREncodingException.java
@@ -0,0 +1,54 @@
+/* DEREncodingException.java --- DER Encoding Exception
+ Copyright (C) 1999,2003 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.security.der;
+
+import java.io.IOException;
+
+public class DEREncodingException extends IOException
+{
+ public DEREncodingException()
+ {
+ super ();
+ }
+
+ public DEREncodingException (String msg)
+ {
+ super (msg);
+ }
+}
diff --git a/libjava/classpath/gnu/java/security/der/DERReader.java b/libjava/classpath/gnu/java/security/der/DERReader.java
new file mode 100644
index 000000000..cd552c8be
--- /dev/null
+++ b/libjava/classpath/gnu/java/security/der/DERReader.java
@@ -0,0 +1,439 @@
+/* DERReader.java -- parses ASN.1 DER sequences
+ Copyright (C) 2003 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.security.der;
+
+import gnu.java.lang.CPStringBuilder;
+
+import gnu.java.security.OID;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+/**
+ * This class decodes DER sequences into Java objects. The methods of
+ * this class do not have knowledge of higher-levels of structure in the
+ * DER stream -- such as ASN.1 constructions -- and it is therefore up
+ * to the calling application to determine if the data are structured
+ * properly by inspecting the {@link DERValue} that is returned.
+ *
+ * @author Casey Marshall (csm@gnu.org)
+ */
+public class DERReader implements DER
+{
+
+ // Fields.
+ // ------------------------------------------------------------------------
+
+ protected InputStream in;
+
+ protected final ByteArrayOutputStream encBuf;
+
+ // Constructor.
+ // ------------------------------------------------------------------------
+
+ /**
+ * Create a new DER reader from a byte array.
+ *
+ * @param in The encoded bytes.
+ */
+ public DERReader(byte[] in)
+ {
+ this(new ByteArrayInputStream(in));
+ }
+
+ public DERReader (byte[] in, int off, int len)
+ {
+ this (new ByteArrayInputStream (in, off, len));
+ }
+
+ /**
+ * Create a new DER readed from an input stream.
+ *
+ * @param in The encoded bytes.
+ */
+ public DERReader(InputStream in)
+ {
+ if (!in.markSupported())
+ this.in = new BufferedInputStream(in, 16384);
+ else
+ this.in = in;
+ encBuf = new ByteArrayOutputStream(2048);
+ }
+
+ // Class methods.
+ // ------------------------------------------------------------------------
+
+ /**
+ * Convenience method for reading a single primitive value from the
+ * given byte array.
+ *
+ * @param encoded The encoded bytes.
+ * @throws IOException If the bytes do not represent an encoded
+ * object.
+ */
+ public static DERValue read(byte[] encoded) throws IOException
+ {
+ return new DERReader(encoded).read();
+ }
+
+ // Instance methods.
+ // ------------------------------------------------------------------------
+
+ public void skip (int bytes) throws IOException
+ {
+ in.skip (bytes);
+ }
+
+ /**
+ * Decode a single value from the input stream, returning it in a new
+ * {@link DERValue}. By "single value" we mean any single type in its
+ * entirety -- including constructed types such as SEQUENCE and all
+ * the values they contain. Usually it is sufficient to call this
+ * method once to parse and return the top-level structure, then to
+ * inspect the returned value for the proper contents.
+ *
+ * @return The parsed DER structure.
+ * @throws IOException If an error occurs reading from the input
+ * stream.
+ * @throws DEREncodingException If the input does not represent a
+ * valid DER stream.
+ */
+ public DERValue read() throws IOException
+ {
+ int tag = in.read();
+ if (tag == -1)
+ throw new EOFException();
+ encBuf.write(tag);
+ int len = readLength();
+ DERValue value = null;
+ if ((tag & CONSTRUCTED) == CONSTRUCTED)
+ {
+ in.mark(2048);
+ byte[] encoded = new byte[len];
+ in.read(encoded);
+ encBuf.write(encoded);
+ value = new DERValue(tag, len, CONSTRUCTED_VALUE, encBuf.toByteArray());
+ in.reset();
+ encBuf.reset();
+ return value;
+ }
+ switch (tag & 0xC0)
+ {
+ case UNIVERSAL:
+ value = new DERValue(tag, len, readUniversal(tag, len),
+ encBuf.toByteArray());
+ encBuf.reset();
+ break;
+ case CONTEXT:
+ byte[] encoded = new byte[len];
+ in.read(encoded);
+ encBuf.write(encoded);
+ value = new DERValue(tag, len, encoded, encBuf.toByteArray());
+ encBuf.reset();
+ break;
+ case APPLICATION:
+ // This should not be reached, since (I think) APPLICATION is
+ // always constructed.
+ throw new DEREncodingException("non-constructed APPLICATION data");
+ default:
+ throw new DEREncodingException("PRIVATE class not supported");
+ }
+ return value;
+ }
+
+ protected int readLength() throws IOException
+ {
+ int i = in.read();
+ if (i == -1)
+ throw new EOFException();
+ encBuf.write(i);
+ if ((i & ~0x7F) == 0)
+ {
+ return i;
+ }
+ else if (i < 0xFF)
+ {
+ byte[] octets = new byte[i & 0x7F];
+ in.read(octets);
+ encBuf.write(octets);
+ return new BigInteger(1, octets).intValue();
+ }
+ throw new DEREncodingException();
+ }
+
+ // Own methods.
+ // ------------------------------------------------------------------------
+
+ private Object readUniversal(int tag, int len) throws IOException
+ {
+ byte[] value = new byte[len];
+ in.read(value);
+ encBuf.write(value);
+ switch (tag & 0x1F)
+ {
+ case BOOLEAN:
+ if (value.length != 1)
+ throw new DEREncodingException();
+ return Boolean.valueOf(value[0] != 0);
+ case NULL:
+ if (len != 0)
+ throw new DEREncodingException();
+ return null;
+ case INTEGER:
+ case ENUMERATED:
+ return new BigInteger(value);
+ case BIT_STRING:
+ byte[] bits = new byte[len - 1];
+ System.arraycopy(value, 1, bits, 0, bits.length);
+ return new BitString(bits, value[0] & 0xFF);
+ case OCTET_STRING:
+ return value;
+ case NUMERIC_STRING:
+ case PRINTABLE_STRING:
+ case T61_STRING:
+ case VIDEOTEX_STRING:
+ case IA5_STRING:
+ case GRAPHIC_STRING:
+ case ISO646_STRING:
+ case GENERAL_STRING:
+ case UNIVERSAL_STRING:
+ case BMP_STRING:
+ case UTF8_STRING:
+ return makeString(tag, value);
+ case UTC_TIME:
+ case GENERALIZED_TIME:
+ return makeTime(tag, value);
+ case OBJECT_IDENTIFIER:
+ return new OID(value);
+ case RELATIVE_OID:
+ return new OID(value, true);
+ default:
+ throw new DEREncodingException("unknown tag " + tag);
+ }
+ }
+
+ private static String makeString(int tag, byte[] value)
+ throws IOException
+ {
+ switch (tag & 0x1F)
+ {
+ case NUMERIC_STRING:
+ case PRINTABLE_STRING:
+ case T61_STRING:
+ case VIDEOTEX_STRING:
+ case IA5_STRING:
+ case GRAPHIC_STRING:
+ case ISO646_STRING:
+ case GENERAL_STRING:
+ return fromIso88591(value);
+
+ case UNIVERSAL_STRING:
+ // XXX The docs say UniversalString is encoded in four bytes
+ // per character, but Java has no support (yet) for UTF-32.
+ //return new String(buf, "UTF-32");
+ case BMP_STRING:
+ return fromUtf16Be(value);
+
+ case UTF8_STRING:
+ return fromUtf8(value);
+
+ default:
+ throw new DEREncodingException("unknown string tag");
+ }
+ }
+
+ private static String fromIso88591(byte[] bytes)
+ {
+ CPStringBuilder str = new CPStringBuilder(bytes.length);
+ for (int i = 0; i < bytes.length; i++)
+ str.append((char) (bytes[i] & 0xFF));
+ return str.toString();
+ }
+
+ private static String fromUtf16Be(byte[] bytes) throws IOException
+ {
+ if ((bytes.length & 0x01) != 0)
+ throw new IOException("UTF-16 bytes are odd in length");
+ CPStringBuilder str = new CPStringBuilder(bytes.length / 2);
+ for (int i = 0; i < bytes.length; i += 2)
+ {
+ char c = (char) ((bytes[i] << 8) & 0xFF);
+ c |= (char) (bytes[i+1] & 0xFF);
+ str.append(c);
+ }
+ return str.toString();
+ }
+
+ private static String fromUtf8(byte[] bytes) throws IOException
+ {
+ CPStringBuilder str = new CPStringBuilder((int)(bytes.length / 1.5));
+ for (int i = 0; i < bytes.length; )
+ {
+ char c = 0;
+ if ((bytes[i] & 0xE0) == 0xE0)
+ {
+ if ((i + 2) >= bytes.length)
+ throw new IOException("short UTF-8 input");
+ c = (char) ((bytes[i++] & 0x0F) << 12);
+ if ((bytes[i] & 0x80) != 0x80)
+ throw new IOException("malformed UTF-8 input");
+ c |= (char) ((bytes[i++] & 0x3F) << 6);
+ if ((bytes[i] & 0x80) != 0x80)
+ throw new IOException("malformed UTF-8 input");
+ c |= (char) (bytes[i++] & 0x3F);
+ }
+ else if ((bytes[i] & 0xC0) == 0xC0)
+ {
+ if ((i + 1) >= bytes.length)
+ throw new IOException("short input");
+ c = (char) ((bytes[i++] & 0x1F) << 6);
+ if ((bytes[i] & 0x80) != 0x80)
+ throw new IOException("malformed UTF-8 input");
+ c |= (char) (bytes[i++] & 0x3F);
+ }
+ else if ((bytes[i] & 0xFF) < 0x80)
+ {
+ c = (char) (bytes[i++] & 0xFF);
+ }
+ else
+ throw new IOException("badly formed UTF-8 sequence");
+ str.append(c);
+ }
+ return str.toString();
+ }
+
+ private Date makeTime(int tag, byte[] value) throws IOException
+ {
+ Calendar calendar = Calendar.getInstance();
+ String str = makeString(PRINTABLE_STRING, value);
+
+ // Classpath's SimpleDateFormat does not work for parsing these
+ // types of times, so we do this by hand.
+ String date = str;
+ String tz = "";
+ if (str.indexOf("+") > 0)
+ {
+ date = str.substring(0, str.indexOf("+"));
+ tz = str.substring(str.indexOf("+"));
+ }
+ else if (str.indexOf("-") > 0)
+ {
+ date = str.substring(0, str.indexOf("-"));
+ tz = str.substring(str.indexOf("-"));
+ }
+ else if (str.endsWith("Z"))
+ {
+ date = str.substring(0, str.length()-2);
+ tz = "Z";
+ }
+ if (!tz.equals("Z") && tz.length() > 0)
+ calendar.setTimeZone(TimeZone.getTimeZone(tz));
+ else
+ calendar.setTimeZone(TimeZone.getTimeZone("UTC"));
+ if ((tag & 0x1F) == UTC_TIME)
+ {
+ if (date.length() < 10) // must be at least 10 chars long
+ throw new DEREncodingException("cannot parse date");
+ // UTCTime is of the form "yyMMddHHmm[ss](Z|(+|-)hhmm)"
+ try
+ {
+ int year = Integer.parseInt(str.substring(0, 2));
+ if (year < 50)
+ year += 2000;
+ else
+ year += 1900;
+ calendar.set(year,
+ Integer.parseInt(str.substring( 2, 4))-1, // month
+ Integer.parseInt(str.substring( 4, 6)), // day
+ Integer.parseInt(str.substring( 6, 8)), // hour
+ Integer.parseInt(str.substring( 8, 10))); // minute
+ if (date.length() == 12)
+ calendar.set(Calendar.SECOND,
+ Integer.parseInt(str.substring(10, 12)));
+ }
+ catch (NumberFormatException nfe)
+ {
+ throw new DEREncodingException("cannot parse date");
+ }
+ }
+ else
+ {
+ if (date.length() < 10) // must be at least 10 chars long
+ throw new DEREncodingException("cannot parse date");
+ // GeneralTime is of the form "yyyyMMddHH[mm[ss[(.|,)SSSS]]]"
+ // followed by "Z" or "(+|-)hh[mm]"
+ try
+ {
+ calendar.set(
+ Integer.parseInt(date.substring(0, 4)), // year
+ Integer.parseInt(date.substring(4, 6))-1, // month
+ Integer.parseInt(date.substring(6, 8)), // day
+ Integer.parseInt(date.substring(8, 10)), 0); // hour, min
+ switch (date.length())
+ {
+ case 19:
+ case 18:
+ case 17:
+ case 16:
+ calendar.set(Calendar.MILLISECOND,
+ Integer.parseInt(date.substring(15)));
+ case 14:
+ calendar.set(Calendar.SECOND,
+ Integer.parseInt(date.substring(12, 14)));
+ case 12:
+ calendar.set(Calendar.MINUTE,
+ Integer.parseInt(date.substring(10, 12)));
+ }
+ }
+ catch (NumberFormatException nfe)
+ {
+ throw new DEREncodingException("cannot parse date");
+ }
+ }
+ return calendar.getTime();
+ }
+}
diff --git a/libjava/classpath/gnu/java/security/der/DERValue.java b/libjava/classpath/gnu/java/security/der/DERValue.java
new file mode 100644
index 000000000..2cbe34573
--- /dev/null
+++ b/libjava/classpath/gnu/java/security/der/DERValue.java
@@ -0,0 +1,189 @@
+/* DERValue.java -- a value read or written to a DER encoding.
+ Copyright (C) 2003 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.security.der;
+
+import gnu.java.security.x509.Util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+public class DERValue implements DER
+{
+
+ // Fields.
+ // ------------------------------------------------------------------------
+
+ private final int tagClass;
+ private final boolean constructed;
+ private final int tag;
+ private int length;
+ private final Object value;
+ private byte[] encoded;
+
+ // Constructor.
+ // ------------------------------------------------------------------------
+
+ public DERValue(int tag, int length, Object value, byte[] encoded)
+ {
+ tagClass = tag & 0xC0;
+ this.tag = tag & 0x1F;
+ constructed = (tag & CONSTRUCTED) == CONSTRUCTED;
+ this.length = length;
+ this.value = value;
+ if (encoded != null)
+ this.encoded = (byte[]) encoded.clone();
+ }
+
+ public DERValue(int tag, Object value)
+ {
+ this(tag, 0, value, null);
+ }
+
+ // Instance methods.
+ // ------------------------------------------------------------------------
+
+ public int getExternalTag()
+ {
+ return tagClass | tag | (constructed ? 0x20 : 0x00);
+ }
+
+ public int getTag()
+ {
+ return tag;
+ }
+
+ public int getTagClass()
+ {
+ return tagClass;
+ }
+
+ public boolean isConstructed()
+ {
+ return constructed;
+ }
+
+ public int getLength()
+ {
+ if (encoded == null)
+ {
+ try
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ length = DERWriter.write(out, this);
+ encoded = out.toByteArray();
+ }
+ catch (IOException ioe)
+ {
+ IllegalArgumentException iae = new IllegalArgumentException ();
+ iae.initCause (ioe);
+ throw iae;
+ }
+ }
+ return length;
+ }
+
+ public Object getValue()
+ {
+ return value;
+ }
+
+ public Object getValueAs (final int derType) throws IOException
+ {
+ byte[] encoded = getEncoded ();
+ encoded[0] = (byte) derType;
+ return DERReader.read (encoded).getValue ();
+ }
+
+ public byte[] getEncoded()
+ {
+ if (encoded == null)
+ {
+ try
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ length = DERWriter.write(out, this);
+ encoded = out.toByteArray();
+ }
+ catch (IOException ioe)
+ {
+ IllegalArgumentException iae = new IllegalArgumentException ();
+ iae.initCause (ioe);
+ throw iae;
+ }
+ }
+ return (byte[]) encoded.clone();
+ }
+
+ public int getEncodedLength()
+ {
+ if (encoded == null)
+ {
+ try
+ {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ length = DERWriter.write(out, this);
+ encoded = out.toByteArray();
+ }
+ catch (IOException ioe)
+ {
+ IllegalArgumentException iae = new IllegalArgumentException ();
+ iae.initCause (ioe);
+ throw iae;
+ }
+ }
+ return encoded.length;
+ }
+
+ public String toString()
+ {
+ String start = "DERValue ( [";
+ if (tagClass == DER.UNIVERSAL)
+ start = start + "UNIVERSAL ";
+ else if (tagClass == DER.PRIVATE)
+ start = start + "PRIVATE ";
+ else if (tagClass == DER.APPLICATION)
+ start = start + "APPLICATION ";
+ start = start + tag + "] constructed=" + constructed + ", value=";
+ if (constructed)
+ start = start + "\n" + Util.hexDump(getEncoded(), "\t");
+ else
+ start = start + value;
+ return start + " )";
+ }
+}
diff --git a/libjava/classpath/gnu/java/security/der/DERWriter.java b/libjava/classpath/gnu/java/security/der/DERWriter.java
new file mode 100644
index 000000000..0c2633605
--- /dev/null
+++ b/libjava/classpath/gnu/java/security/der/DERWriter.java
@@ -0,0 +1,355 @@
+/* DERWriter.java -- write Java types in DER format.
+ Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. */
+
+
+package gnu.java.security.der;
+
+import gnu.java.security.OID;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+import java.math.BigInteger;
+
+import java.text.SimpleDateFormat;
+
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.TimeZone;
+
+/**
+ * Methods that allow various Java types to be written as a DER
+ * (Distinguished Encoding Rules) stream to the specified output stream.
+ * DER is used to encode ASN.1 constructions, but this class provides no
+ * methods for interacting with ASN.1. Rather, callers should construct
+ * their output objects properly for whatever ASN.1 construct is being
+ * output.
+ *
+ * <p>This class only defines static methods; there are no instance
+ * variables needed.
+ *
+ * @author Casey Marshall (csm@gnu.org)
+ */
+public class DERWriter implements DER
+{
+
+ // Constructors.
+ // ------------------------------------------------------------------------
+
+ /** This class only has static methods. */
+ private DERWriter()
+ {
+ }
+
+ // Class methods.
+ // ------------------------------------------------------------------------
+
+ public static int write(OutputStream out, DERValue object)
+ throws IOException
+ {
+ if (DER.CONSTRUCTED_VALUE.equals (object.getValue ()))
+ {
+ out.write (object.getEncoded ());
+ return object.getLength ();
+ }
+
+ out.write(object.getExternalTag());
+ Object value = object.getValue();
+ if (value == null)
+ {
+ writeLength(out, 0);
+ return 0;
+ }
+ if (value instanceof Boolean)
+ return writeBoolean(out, (Boolean) value);
+ else if (value instanceof BigInteger)
+ return writeInteger(out, (BigInteger) value);
+ else if (value instanceof Date)
+ return writeDate(out, object.getExternalTag(), (Date) value);
+ else if (value instanceof String)
+ return writeString(out, object.getExternalTag(), (String) value);
+ else if (value instanceof List)
+ return writeSequence(out, (List) value);
+ else if (value instanceof Set)
+ return writeSet(out, (Set) value);
+ else if (value instanceof BitString)
+ return writeBitString(out, (BitString) value);
+ else if (value instanceof OID)
+ return writeOID(out, (OID) value);
+ else if (value instanceof byte[])
+ {
+ writeLength(out, ((byte[]) value).length);
+ out.write((byte[]) value);
+ return ((byte[]) value).length;
+ }
+ else if (value instanceof DERValue)
+ {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ write(bout, (DERValue) value);
+ byte[] buf = bout.toByteArray();
+ writeLength(out, buf.length);
+ out.write(buf);
+ return buf.length;
+ }
+ else
+ throw new DEREncodingException("cannot encode " + value.getClass().getName());
+ }
+
+ public static int definiteEncodingSize(int length)
+ {
+ if (length < 128)
+ return 1;
+ else if (length < 256)
+ return 2;
+ else if (length < 65536)
+ return 3;
+ else if (length < 16777216)
+ return 4;
+ else
+ return 5;
+ }
+
+ // Own methods.
+ // ------------------------------------------------------------------------
+
+ /**
+ * Write a BOOLEAN type to the given output stream.
+ *
+ * @param out The sink output stream.
+ * @param b The boolean value to write.
+ */
+ private static int writeBoolean(OutputStream out, Boolean b)
+ throws IOException
+ {
+ writeLength(out, 1);
+ if (b.booleanValue())
+ out.write(0xFF);
+ else
+ out.write(0);
+ return 1;
+ }
+
+ /**
+ * Write an INTEGER type to the given output stream.
+ *
+ * @param out The sink output stream.
+ * @param integer The integer to write.
+ */
+ private static int writeInteger(OutputStream out, BigInteger integer)
+ throws IOException
+ {
+ byte[] bytes = integer.toByteArray();
+ writeLength(out, bytes.length);
+ out.write(bytes);
+ return bytes.length;
+ }
+
+ private static int writeSequence(OutputStream out, List sequence)
+ throws IOException
+ {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ for (Iterator i = sequence.iterator(); i.hasNext(); )
+ {
+ write(bout, (DERValue) i.next());
+ }
+ byte[] buf = bout.toByteArray();
+ writeLength(out, buf.length);
+ out.write(buf);
+ return buf.length;
+ }
+
+ private static int writeSet(OutputStream out, Set set)
+ throws IOException
+ {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ for (Iterator i = set.iterator(); i.hasNext(); )
+ {
+ write(bout, (DERValue) i.next());
+ }
+ byte[] buf = bout.toByteArray();
+ writeLength(out, buf.length);
+ out.write(buf);
+ return buf.length;
+ }
+
+ private static int writeOID(OutputStream out, OID oid)
+ throws IOException
+ {
+ byte[] der = oid.getDER();
+ writeLength(out, der.length);
+ out.write(der);
+ return der.length;
+ }
+
+ private static int writeBitString(OutputStream out, BitString bs)
+ throws IOException
+ {
+ byte[] buf = bs.getShiftedByteArray();
+ writeLength(out, buf.length + 1);
+ out.write(bs.getIgnoredBits());
+ out.write(buf);
+ return buf.length + 1;
+ }
+
+ private static int writeString(OutputStream out, int tag, String str)
+ throws IOException
+ {
+ byte[] b = null;
+ switch (tag & 0x1F)
+ {
+ case NUMERIC_STRING:
+ case PRINTABLE_STRING:
+ case T61_STRING:
+ case VIDEOTEX_STRING:
+ case IA5_STRING:
+ case GRAPHIC_STRING:
+ case ISO646_STRING:
+ case GENERAL_STRING:
+ b = toIso88591(str);
+ break;
+
+ case UNIVERSAL_STRING:
+ case BMP_STRING:
+ b = toUtf16Be(str);
+ break;
+
+ case UTF8_STRING:
+ default:
+ b = toUtf8(str);
+ break;
+ }
+ writeLength(out, b.length);
+ out.write(b);
+ return b.length;
+ }
+
+ private static byte[] toIso88591(String string)
+ {
+ byte[] result = new byte[string.length()];
+ for (int i = 0; i < string.length(); i++)
+ result[i] = (byte) string.charAt(i);
+ return result;
+ }
+
+ private static byte[] toUtf16Be(String string)
+ {
+ byte[] result = new byte[string.length() * 2];
+ for (int i = 0; i < string.length(); i++)
+ {
+ result[i*2 ] = (byte) ((string.charAt(i) >>> 8) & 0xFF);
+ result[i*2+1] = (byte) (string.charAt(i) & 0xFF);
+ }
+ return result;
+ }
+
+ private static byte[] toUtf8(String string)
+ {
+ ByteArrayOutputStream buf =
+ new ByteArrayOutputStream((int)(string.length() * 1.5));
+ for (int i = 0; i < string.length(); i++)
+ {
+ char c = string.charAt(i);
+ if (c < 0x0080)
+ buf.write(c & 0xFF);
+ else if (c < 0x0800)
+ {
+ buf.write(0xC0 | ((c >>> 6) & 0x3F));
+ buf.write(0x80 | (c & 0x3F));
+ }
+ else
+ {
+ buf.write(0xE0 | ((c >>> 12) & 0x0F));
+ buf.write(0x80 | ((c >>> 6) & 0x3F));
+ buf.write(0x80 | (c & 0x3F));
+ }
+ }
+ return buf.toByteArray();
+ }
+
+ private static int writeDate(OutputStream out, int tag, Date date)
+ throws IOException
+ {
+ SimpleDateFormat sdf = null;
+ if ((tag & 0x1F) == UTC_TIME)
+ sdf = new SimpleDateFormat("yyMMddHHmmss'Z'");
+ else
+ sdf = new SimpleDateFormat("yyyyMMddHHmmss'.'SSS'Z'");
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ byte[] b = sdf.format(date).getBytes("ISO-8859-1");
+ writeLength(out, b.length);
+ out.write(b);
+ return b.length;
+ }
+
+ // Package method.
+ // ------------------------------------------------------------------------
+
+ static void writeLength(OutputStream out, int len) throws IOException
+ {
+ if (len < 128)
+ out.write(len);
+ else if (len < 256)
+ {
+ out.write(0x81);
+ out.write(len);
+ }
+ else if (len < 65536)
+ {
+ out.write(0x82);
+ out.write(len >> 8);
+ out.write(len);
+ }
+ else if (len < 16777216)
+ {
+ out.write(0x83);
+ out.write(len >> 16);
+ out.write(len >> 8);
+ out.write(len);
+ }
+ else
+ {
+ out.write(0x84);
+ out.write(len >> 24);
+ out.write(len >> 16);
+ out.write(len >> 8);
+ out.write(len);
+ }
+ }
+}
diff --git a/libjava/classpath/gnu/java/security/der/package.html b/libjava/classpath/gnu/java/security/der/package.html
new file mode 100644
index 000000000..e74b0db4e
--- /dev/null
+++ b/libjava/classpath/gnu/java/security/der/package.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
+<!-- package.html - describes classes in gnu.java.security.der package.
+ Copyright (C) 2005 Free Software Foundation, Inc.
+
+This file is part of GNU Classpath.
+
+GNU Classpath 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, or (at your option)
+any later version.
+
+GNU Classpath 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 GNU Classpath; see the file COPYING. If not, write to the
+Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+02110-1301 USA.
+
+Linking this library statically or dynamically with other modules is
+making a combined work based on this library. Thus, the terms and
+conditions of the GNU General Public License cover the whole
+combination.
+
+As a special exception, the copyright holders of this library give you
+permission to link this library with independent modules to produce an
+executable, regardless of the license terms of these independent
+modules, and to copy and distribute the resulting executable under
+terms of your choice, provided that you also meet, for each linked
+independent module, the terms and conditions of the license of that
+module. An independent module is a module which is not derived from
+or based on this library. If you modify this library, you may extend
+this exception to your version of the library, but you are not
+obligated to do so. If you do not wish to do so, delete this
+exception statement from your version. -->
+
+<html>
+<head><title>GNU Classpath - gnu.java.security.der</title></head>
+
+<body>
+<p></p>
+
+</body>
+</html>