From: megacz Date: Fri, 30 Jan 2004 06:45:03 +0000 (+0000) Subject: 2002/03/21 01:19:32 X-Git-Tag: RC3~1867 X-Git-Url: http://git.megacz.com/?p=org.ibex.core.git;a=commitdiff_plain;h=e5e9355b4f4e0e2c8de9068a71c1e3cc26fa9905 2002/03/21 01:19:32 darcs-hash:20040130064503-2ba56-caff6813a1e43e4303cb1a078470e2b10739b027.gz --- diff --git a/src/org/bouncycastle/asn1/BERConstructedOctetString.java b/src/org/bouncycastle/asn1/BERConstructedOctetString.java new file mode 100644 index 0000000..47b4245 --- /dev/null +++ b/src/org/bouncycastle/asn1/BERConstructedOctetString.java @@ -0,0 +1,145 @@ +package org.bouncycastle.asn1; + +import java.io.*; +import java.util.*; + +public class BERConstructedOctetString + extends DEROctetString +{ + /** + * convert a vector of octet strings into a single byte string + */ + static private byte[] toBytes( + Vector octs) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + + for (int i = 0; i != octs.size(); i++) + { + DEROctetString o = (DEROctetString)octs.elementAt(i); + + try + { + bOut.write(o.getOctets()); + } + catch (IOException e) + { + throw new RuntimeException("exception converting octets " + e.toString()); + } + } + + return bOut.toByteArray(); + } + + private Vector octs; + + /** + * @param string the octets making up the octet string. + */ + public BERConstructedOctetString( + byte[] string) + { + super(string); + } + + public BERConstructedOctetString( + Vector octs) + { + super(toBytes(octs)); + + this.octs = octs; + } + + public BERConstructedOctetString( + DERObject obj) + { + super(obj); + } + + public BERConstructedOctetString( + DEREncodable obj) + { + super(obj.getDERObject()); + } + + public byte[] getOctets() + { + return string; + } + + public Vector getDEROctets() + { + if (octs == null) + { + octs = generateOcts(); + } + + return octs; + } + + private Vector generateOcts() + { + int start = 0; + int end = 0; + Vector vec = new Vector(); + + while (end < string.length) + { + if ((end + 1) < string.length) + { + if (string[end] == 0 && string[end + 1] == 0) + { + byte[] nStr = new byte[end - start + 1]; + + for (int i = 0; i != nStr.length; i++) + { + nStr[i] = string[start + i]; + } + + vec.addElement(new DEROctetString(nStr)); + start = end + 1; + } + } + end++; + } + + byte[] nStr = new byte[end - start]; + for (int i = 0; i != nStr.length; i++) + { + nStr[i] = string[start + i]; + } + + vec.addElement(new DEROctetString(nStr)); + + return vec; + } + + public void encode( + DEROutputStream out) + throws IOException + { + if (out instanceof BEROutputStream) + { + out.write(CONSTRUCTED | OCTET_STRING); + + out.write(0x80); + + if (octs == null) + { + octs = generateOcts(); + } + + for (int i = 0; i != octs.size(); i++) + { + out.writeObject(octs.elementAt(i)); + } + + out.write(0x00); + out.write(0x00); + } + else + { + super.encode(out); + } + } +} diff --git a/src/org/bouncycastle/asn1/BERConstructedSequence.java b/src/org/bouncycastle/asn1/BERConstructedSequence.java new file mode 100644 index 0000000..b8a3c2d --- /dev/null +++ b/src/org/bouncycastle/asn1/BERConstructedSequence.java @@ -0,0 +1,49 @@ +package org.bouncycastle.asn1; + +import java.io.*; +import java.util.*; + +public class BERConstructedSequence + extends DERConstructedSequence +{ + /* + * A note on the implementation: + *

+ * As DER requires the constructed, definite-length model to + * be used for structured types, this varies slightly from the + * ASN.1 descriptions given. Rather than just outputing SEQUENCE, + * we also have to specify CONSTRUCTED, and the objects length. + */ + void encode( + DEROutputStream out) + throws IOException + { + if (out instanceof BEROutputStream) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BEROutputStream dOut = new BEROutputStream(bOut); + Enumeration e = getObjects(); + + while (e.hasMoreElements()) + { + Object obj = e.nextElement(); + + dOut.writeObject(obj); + } + + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + out.write(SEQUENCE | CONSTRUCTED); + out.write(0x80); + out.write(bytes); + out.write(0x00); + out.write(0x00); + } + else + { + super.encode(out); + } + } +} diff --git a/src/org/bouncycastle/asn1/BERInputStream.java b/src/org/bouncycastle/asn1/BERInputStream.java new file mode 100644 index 0000000..824b439 --- /dev/null +++ b/src/org/bouncycastle/asn1/BERInputStream.java @@ -0,0 +1,159 @@ +package org.bouncycastle.asn1; + +import java.math.BigInteger; +import java.io.*; +import java.util.*; + +public class BERInputStream + extends DERInputStream +{ + private DERObject END_OF_STREAM = new DERObject() { + void encode( + DEROutputStream out) + throws IOException + { + throw new IOException("Eeek!"); + } + + }; + public BERInputStream( + InputStream is) + { + super(is); + } + + /** + * read a string of bytes representing an indefinite length object. + */ + private byte[] readIndefiniteLengthFully() + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + int b, b1; + + b1 = read(); + + while ((b = read()) >= 0) + { + if (b1 == 0 && b == 0) + { + break; + } + + bOut.write(b1); + b1 = b; + } + + return bOut.toByteArray(); + } + + private BERConstructedOctetString buildConstructedOctetString( + DEROctetString o1, + DEROctetString o2) + throws IOException + { + Vector octs = new Vector(); + + if (o1 != null) + { + octs.addElement(o1); + octs.addElement(o2); + } + + for (;;) + { + DERObject o = readObject(); + + if (o == END_OF_STREAM) + { + break; + } + + octs.addElement(o); + } + + return new BERConstructedOctetString(octs); + } + + public DERObject readObject() + throws IOException + { + int tag = read(); + if (tag == -1) + { + throw new EOFException(); + } + + int length = readLength(); + + if (length < 0) // indefinite length method + { + byte[] bytes; + + switch (tag) + { + case NULL: + return null; + case SEQUENCE | CONSTRUCTED: + BERConstructedSequence seq = new BERConstructedSequence(); + + for (;;) + { + DERObject obj = readObject(); + + if (obj == END_OF_STREAM) + { + break; + } + + seq.addObject(obj); + } + return seq; + case OCTET_STRING | CONSTRUCTED: + return buildConstructedOctetString(null, null); + default: + if ((tag & (TAGGED | CONSTRUCTED)) != 0) + { + // with tagged object tag number is bottom 4 bits + BERTaggedObject tagObj = new BERTaggedObject(tag & 0x0f, readObject()); + DERObject o = readObject(); + + if (o == END_OF_STREAM) + { + return tagObj; + } + else if (o instanceof DEROctetString + && tagObj.getObject() instanceof DEROctetString) + { + // + // it's an implicit object - mark it as so... + // + tagObj = new BERTaggedObject(false, tag & 0x0f, + buildConstructedOctetString((DEROctetString)tagObj.getObject(), (DEROctetString)o)); + + return tagObj; + } + + throw new IOException("truncated tagged object"); + } + + bytes = readIndefiniteLengthFully(); + + return buildObject(tag, bytes); + } + } + else + { + if (tag == 0 && length == 0) // end of contents marker. + { + return END_OF_STREAM; + } + + byte[] bytes = new byte[length]; + + readFully(bytes); + + return buildObject(tag, bytes); + } + } +} diff --git a/src/org/bouncycastle/asn1/BEROutputStream.java b/src/org/bouncycastle/asn1/BEROutputStream.java new file mode 100644 index 0000000..4adefed --- /dev/null +++ b/src/org/bouncycastle/asn1/BEROutputStream.java @@ -0,0 +1,35 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +public class BEROutputStream + extends DEROutputStream +{ + public BEROutputStream( + OutputStream os) + { + super(os); + } + + public void writeObject( + Object obj) + throws IOException + { + if (obj == null) + { + writeNull(); + } + else if (obj instanceof DERObject) + { + ((DERObject)obj).encode(this); + } + else if (obj instanceof DEREncodable) + { + ((DEREncodable)obj).getDERObject().encode(this); + } + else + { + throw new IOException("object not BEREncodable"); + } + } +} diff --git a/src/org/bouncycastle/asn1/BERTaggedObject.java b/src/org/bouncycastle/asn1/BERTaggedObject.java new file mode 100644 index 0000000..b24f037 --- /dev/null +++ b/src/org/bouncycastle/asn1/BERTaggedObject.java @@ -0,0 +1,97 @@ +package org.bouncycastle.asn1; + +import java.io.*; +import java.util.*; + +/** + * BER TaggedObject - in ASN.1 nottation this is any object proceeded by + * a [n] where n is some number - these are assume to follow the construction + * rules (as with sequences). + */ +public class BERTaggedObject + extends DERTaggedObject +{ + /** + * This creates an empty tagged object of tagNo (ie. zero length). + * + * @param tagNo the tag number for this object. + */ + public BERTaggedObject( + int tagNo) + { + super(tagNo); + } + + /** + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public BERTaggedObject( + int tagNo, + DERObject obj) + { + super(tagNo, obj); + } + + /** + * @param explicit true if an explicitly tagged object. + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public BERTaggedObject( + boolean explicit, + int tagNo, + DERObject obj) + { + super(explicit, tagNo, obj); + } + + void encode( + DEROutputStream out) + throws IOException + { + if (out instanceof BEROutputStream) + { + out.write(CONSTRUCTED | TAGGED | tagNo); + out.write(0x80); + + if (!empty) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + BEROutputStream dOut = new BEROutputStream(bOut); + + if (!explicit) + { + if (obj instanceof BERConstructedOctetString) + { + Vector octs = ((BERConstructedOctetString)obj).getDEROctets(); + + for (int i = 0; i != octs.size(); i++) + { + dOut.writeObject(octs.elementAt(i)); + } + } + else + { + dOut.writeObject(obj); // hmmm... + } + } + else + { + dOut.writeObject(obj); + } + + dOut.close(); + + out.write(bOut.toByteArray()); + } + + out.write(0x00); + out.write(0x00); + } + else + { + super.encode(out); + } + } +} diff --git a/src/org/bouncycastle/asn1/DERBMPString.java b/src/org/bouncycastle/asn1/DERBMPString.java new file mode 100644 index 0000000..8a58b4a --- /dev/null +++ b/src/org/bouncycastle/asn1/DERBMPString.java @@ -0,0 +1,59 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +/** + * DER BMPString object. + */ +public class DERBMPString + extends DERObject + implements DERString +{ + String string; + + /** + * basic constructor - byte encoded string. + */ + public DERBMPString( + byte[] string) + { + try + { + this.string = new String(string, "UnicodeBig"); + } + catch (UnsupportedEncodingException e) + { + throw new RuntimeException(e.toString()); + } + } + + /** + * basic constructor + */ + public DERBMPString( + String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + void encode( + DEROutputStream out) + throws IOException + { + char[] c = string.toCharArray(); + byte[] b = new byte[c.length * 2]; + + for (int i = 0; i != c.length; i++) + { + b[2 * i] = (byte)((c[i] & 0xff00) >> 8); + b[2 * i + 1] = (byte)c[i]; + } + + out.writeEncoded(BMP_STRING, b); + } +} diff --git a/src/org/bouncycastle/asn1/DERBitString.java b/src/org/bouncycastle/asn1/DERBitString.java new file mode 100644 index 0000000..0c888d5 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERBitString.java @@ -0,0 +1,85 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +public class DERBitString + extends DERObject +{ + protected byte[] data; + protected int padBits; + + protected DERBitString( + byte data, + int padBits) + { + this.data = new byte[1]; + this.data[0] = data; + this.padBits = padBits; + } + + /** + * @param data the octets making up the bit string. + * @param padBits the number of extra bits at the end of the string. + */ + public DERBitString( + byte[] data, + int padBits) + { + this.data = data; + this.padBits = padBits; + } + + public DERBitString( + byte[] data) + { + this(data, 0); + } + + public DERBitString( + DERObject obj) + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + + dOut.writeObject(obj); + dOut.close(); + + this.data = bOut.toByteArray(); + this.padBits = 0; + } + catch (IOException e) + { + throw new IllegalArgumentException("Error processing object : " + e.toString()); + } + } + + public DERBitString( + DEREncodable obj) + { + this(obj.getDERObject()); + } + + public byte[] getBytes() + { + return data; + } + + public int getPadBits() + { + return padBits; + } + + void encode( + DEROutputStream out) + throws IOException + { + byte[] bytes = new byte[getBytes().length + 1]; + + bytes[0] = (byte)getPadBits(); + System.arraycopy(getBytes(), 0, bytes, 1, bytes.length - 1); + + out.writeEncoded(BIT_STRING, bytes); + } +} diff --git a/src/org/bouncycastle/asn1/DERBoolean.java b/src/org/bouncycastle/asn1/DERBoolean.java new file mode 100644 index 0000000..4fce317 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERBoolean.java @@ -0,0 +1,37 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +public class DERBoolean + extends DERObject +{ + byte value; + + public DERBoolean( + byte[] value) + { + this.value = value[0]; + } + + public DERBoolean( + boolean value) + { + this.value = (value) ? (byte)0xff : (byte)0; + } + + public boolean isTrue() + { + return (value != 0); + } + + void encode( + DEROutputStream out) + throws IOException + { + byte[] bytes = new byte[1]; + + bytes[0] = value; + + out.writeEncoded(BOOLEAN, bytes); + } +} diff --git a/src/org/bouncycastle/asn1/DERConstructedSequence.java b/src/org/bouncycastle/asn1/DERConstructedSequence.java new file mode 100644 index 0000000..d600d95 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERConstructedSequence.java @@ -0,0 +1,77 @@ +package org.bouncycastle.asn1; + +import java.io.*; +import java.util.*; + +public class DERConstructedSequence + extends DERObject +{ + private Vector seq = new Vector(); + + public DERConstructedSequence() + { + } + + public void addObject( + DEREncodable obj) + { + seq.addElement(obj); + } + + public Enumeration getObjects() + { + return seq.elements(); + } + + /** + * return the object at the sequence postion indicated by index. + * + * @param the sequence number (starting at zero) of the object + * @return the object at the sequence postion indicated by index. + */ + public Object getObjectAt( + int index) + { + return seq.elementAt(index); + } + + /** + * return the number of objects in this sequence. + * + * @return the number of objects in this sequence. + */ + public int getSize() + { + return seq.size(); + } + + /* + * A note on the implementation: + *

+ * As DER requires the constructed, definite-length model to + * be used for structured types, this varies slightly from the + * ASN.1 descriptions given. Rather than just outputing SEQUENCE, + * we also have to specify CONSTRUCTED, and the objects length. + */ + void encode( + DEROutputStream out) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + Enumeration e = getObjects(); + + while (e.hasMoreElements()) + { + Object obj = e.nextElement(); + + dOut.writeObject(obj); + } + + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + out.writeEncoded(SEQUENCE | CONSTRUCTED, bytes); + } +} diff --git a/src/org/bouncycastle/asn1/DERConstructedSet.java b/src/org/bouncycastle/asn1/DERConstructedSet.java new file mode 100644 index 0000000..f675b42 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERConstructedSet.java @@ -0,0 +1,77 @@ +package org.bouncycastle.asn1; + +import java.io.*; +import java.util.*; + +public class DERConstructedSet + extends DERObject +{ + private Vector set = new Vector(); + + public DERConstructedSet() + { + } + + public void addObject( + DEREncodable obj) + { + set.addElement(obj); + } + + public Enumeration getObjects() + { + return set.elements(); + } + + /** + * return the object at the set postion indicated by index. + * + * @param the set number (starting at zero) of the object + * @return the object at the set postion indicated by index. + */ + public Object getObjectAt( + int index) + { + return set.elementAt(index); + } + + /** + * return the number of objects in this set. + * + * @return the number of objects in this set. + */ + public int getSize() + { + return set.size(); + } + + /* + * A note on the implementation: + *

+ * As DER requires the constructed, definite-length model to + * be used for structured types, this varies slightly from the + * ASN.1 descriptions given. Rather than just outputing SET, + * we also have to specify CONSTRUCTED, and the objects length. + */ + void encode( + DEROutputStream out) + throws IOException + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + Enumeration e = getObjects(); + + while (e.hasMoreElements()) + { + Object obj = e.nextElement(); + + dOut.writeObject(obj); + } + + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + out.writeEncoded(SET | CONSTRUCTED, bytes); + } +} diff --git a/src/org/bouncycastle/asn1/DEREncodable.java b/src/org/bouncycastle/asn1/DEREncodable.java new file mode 100644 index 0000000..d89305a --- /dev/null +++ b/src/org/bouncycastle/asn1/DEREncodable.java @@ -0,0 +1,6 @@ +package org.bouncycastle.asn1; + +public interface DEREncodable +{ + public DERObject getDERObject(); +} diff --git a/src/org/bouncycastle/asn1/DERIA5String.java b/src/org/bouncycastle/asn1/DERIA5String.java new file mode 100644 index 0000000..5181bdd --- /dev/null +++ b/src/org/bouncycastle/asn1/DERIA5String.java @@ -0,0 +1,62 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +/** + * DER IA5String object - this is an ascii string. + */ +public class DERIA5String + extends DERObject + implements DERString +{ + String string; + + /** + * basic constructor - with bytes. + */ + public DERIA5String( + byte[] string) + { + try + { + this.string = new String(string, "US-ASCII"); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException("PANIC: " + e); + } + } + + /** + * basic constructor - with string. + */ + public DERIA5String( + String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + public byte[] getOctets() + { + try + { + return string.getBytes("US-ASCII"); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException("PANIC: " + e); + } + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(IA5_STRING, this.getOctets()); + } +} diff --git a/src/org/bouncycastle/asn1/DERInputStream.java b/src/org/bouncycastle/asn1/DERInputStream.java new file mode 100644 index 0000000..61de735 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERInputStream.java @@ -0,0 +1,243 @@ +package org.bouncycastle.asn1; + +import java.math.BigInteger; +import java.io.*; + +public class DERInputStream + extends FilterInputStream implements DERTags +{ + public DERInputStream( + InputStream is) + { + super(is); + } + + protected int readLength() + throws IOException + { + int length = read(); + if (length < 0) + { + throw new IOException("EOF found when length expected"); + } + + if (length == 0x80) + { + return -1; // indefinite-length encoding + } + + if (length > 127) + { + int size = length & 0x7f; + + length = 0; + for (int i = 0; i < size; i++) + { + int next = read(); + + if (next < 0) + { + throw new IOException("EOF found reading length"); + } + + length = (length << 8) + next; + } + } + + return length; + } + + protected void readFully( + byte[] bytes) + throws IOException + { + int left = bytes.length; + + if (left == 0) + { + return; + } + + while ((left -= read(bytes, bytes.length - left, left)) != 0) + { + ; + } + } + + /** + * build an object given its tag and a byte stream to construct it + * from. + */ + protected DERObject buildObject( + int tag, + byte[] bytes) + throws IOException + { + switch (tag) + { + case NULL: + return null; + case SEQUENCE | CONSTRUCTED: + ByteArrayInputStream bIn = new ByteArrayInputStream(bytes); + BERInputStream dIn = new BERInputStream(bIn); + DERConstructedSequence seq = new DERConstructedSequence(); + + try + { + for (;;) + { + DERObject obj = dIn.readObject(); + + seq.addObject(obj); + } + } + catch (EOFException ex) + { + return seq; + } + case SET | CONSTRUCTED: + bIn = new ByteArrayInputStream(bytes); + dIn = new BERInputStream(bIn); + + DERSet set = new DERSet(dIn.readObject()); + + try + { + for (;;) + { + DERObject obj = dIn.readObject(); + + set.addObject(obj); + } + } + catch (EOFException ex) + { + return set; + } + case BOOLEAN: + return new DERBoolean(bytes); + case INTEGER: + return new DERInteger(bytes); + case OBJECT_IDENTIFIER: + int head = bytes[0] & 0xff; + StringBuffer objId = new StringBuffer(); + + objId.append(Integer.toString(head / 40)); + objId.append('.'); + objId.append(Integer.toString(head % 40)); + + int value = 0; + + for (int i = 1; i != bytes.length; i++) + { + int b = bytes[i] & 0xff; + + value = value * 128 + (b & 0x7f); + if ((b & 128) == 0) // end of number reached + { + objId.append('.'); + objId.append(Integer.toString(value)); + value = 0; + } + } + + return new DERObjectIdentifier(objId.toString()); + case BIT_STRING: + int padBits = bytes[0]; + byte[] data = new byte[bytes.length - 1]; + + System.arraycopy(bytes, 1, data, 0, bytes.length - 1); + + return new DERBitString(data, padBits); + case PRINTABLE_STRING: + return new DERPrintableString(bytes); + case IA5_STRING: + return new DERIA5String(bytes); + case T61_STRING: + return new DERT61String(bytes); + case VISIBLE_STRING: + return new DERVisibleString(bytes); + case BMP_STRING: + return new DERBMPString(bytes); + case OCTET_STRING: + return new DEROctetString(bytes); + case UTC_TIME: + return new DERUTCTime(new String(bytes)); + default: + // + // with tagged object tag number is bottom 4 bits + // + if ((tag & (TAGGED | CONSTRUCTED)) != 0) + { + if (bytes.length == 0) // empty tag! + { + return new DERTaggedObject(tag & 0x0f); + } + + // + // simple type - implicit... return an octet string + // + if ((tag & CONSTRUCTED) == 0) + { + return new DERTaggedObject(false, tag & 0x0f, new DEROctetString(bytes)); + } + + bIn = new ByteArrayInputStream(bytes); + dIn = new BERInputStream(bIn); + + DEREncodable dObj = dIn.readObject(); + + // + // explicitly tagged (probably!) - if it isn't we'd have to + // tell from the context + // + if (dIn.available() == 0) + { + return new DERTaggedObject(tag & 0x0f, dObj); + } + + // + // another implicit object, we'll create a sequence... + // + seq = new DERConstructedSequence(); + + seq.addObject(dObj); + + try + { + for (;;) + { + dObj = dIn.readObject(); + + seq.addObject(dObj); + } + } + catch (EOFException ex) + { + // ignore -- + } + + return new DERTaggedObject(false, tag & 0x0f, seq); + } + + return new DERUnknownTag(tag, bytes); + } + } + + public DERObject readObject() + throws IOException + { + int tag = read(); + if (tag == -1) + { + throw new EOFException(); + } + + int length = readLength(); + byte[] bytes = new byte[length]; + + readFully(bytes); + + return buildObject(tag, bytes); + } +} diff --git a/src/org/bouncycastle/asn1/DERInteger.java b/src/org/bouncycastle/asn1/DERInteger.java new file mode 100644 index 0000000..0f86910 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERInteger.java @@ -0,0 +1,49 @@ +package org.bouncycastle.asn1; + +import java.io.*; +import java.math.BigInteger; + +public class DERInteger + extends DERObject +{ + byte[] bytes; + + public DERInteger( + int value) + { + bytes = BigInteger.valueOf(value).toByteArray(); + } + + public DERInteger( + BigInteger value) + { + bytes = value.toByteArray(); + } + + public DERInteger( + byte[] bytes) + { + this.bytes = bytes; + } + + public BigInteger getValue() + { + return new BigInteger(bytes); + } + + /** + * in some cases positive values get crammed into a space, + * that's not quite big enough... + */ + public BigInteger getPositiveValue() + { + return new BigInteger(1, bytes); + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(INTEGER, bytes); + } +} diff --git a/src/org/bouncycastle/asn1/DERObject.java b/src/org/bouncycastle/asn1/DERObject.java new file mode 100644 index 0000000..fb75442 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERObject.java @@ -0,0 +1,15 @@ +package org.bouncycastle.asn1; + +import java.io.IOException; + +public abstract class DERObject + implements DERTags, DEREncodable +{ + abstract void encode(DEROutputStream out) + throws IOException; + + public DERObject getDERObject() + { + return this; + } +} diff --git a/src/org/bouncycastle/asn1/DERObjectIdentifier.java b/src/org/bouncycastle/asn1/DERObjectIdentifier.java new file mode 100644 index 0000000..b22c7ae --- /dev/null +++ b/src/org/bouncycastle/asn1/DERObjectIdentifier.java @@ -0,0 +1,77 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +public class DERObjectIdentifier + extends DERObject +{ + String identifier; + + public DERObjectIdentifier( + String identifier) + { + this.identifier = identifier; + } + + public String getId() + { + return identifier; + } + + void encode( + DEROutputStream out) + throws IOException + { + OIDTokenizer tok = new OIDTokenizer(identifier); + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + + // space for 5 7 bit numbers in an int + byte[] iBuf = new byte[5]; + + dOut.write( + Integer.parseInt(tok.nextToken()) * 40 + + Integer.parseInt(tok.nextToken())); + + while (tok.hasMoreTokens()) + { + // + // translate into base 128 + // + int value = Integer.parseInt(tok.nextToken()); + int count = iBuf.length - 1; + + iBuf[count--] = (byte)(value % 128); + value /= 128; + + while (value != 0) + { + iBuf[count--] = (byte)((value % 128) | 0x80); + value /= 128; + } + dOut.write(iBuf, count + 1, iBuf.length - (count + 1)); + } + + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + out.writeEncoded(OBJECT_IDENTIFIER, bytes); + } + + public int hashCode() + { + return identifier.hashCode(); + } + + public boolean equals( + Object o) + { + if ((o == null) || !(o instanceof DERObjectIdentifier)) + { + return false; + } + + return identifier.equals(((DERObjectIdentifier)o).identifier); + } +} diff --git a/src/org/bouncycastle/asn1/DEROctetString.java b/src/org/bouncycastle/asn1/DEROctetString.java new file mode 100644 index 0000000..fd9f516 --- /dev/null +++ b/src/org/bouncycastle/asn1/DEROctetString.java @@ -0,0 +1,97 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +public class DEROctetString + extends DERObject +{ + byte[] string; + + /** + * @param string the octets making up the octet string. + */ + public DEROctetString( + byte[] string) + { + this.string = string; + } + + public DEROctetString( + DERObject obj) + { + try + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + + dOut.writeObject(obj); + dOut.close(); + + this.string = bOut.toByteArray(); + } + catch (IOException e) + { + throw new IllegalArgumentException("Error processing object : " + e.toString()); + } + } + + public DEROctetString( + DEREncodable obj) + { + this(obj.getDERObject()); + } + + public byte[] getOctets() + { + return string; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(OCTET_STRING, string); + } + + public int hashCode() + { + byte[] b = this.getOctets(); + int value = 0; + + for (int i = 0; i != b.length; i++) + { + value ^= (b[i] & 0xff) << (i % 4); + } + + return value; + } + + public boolean equals( + Object o) + { + if (o == null || !(o instanceof DEROctetString)) + { + return false; + } + + DEROctetString other = (DEROctetString)o; + + if (other.getOctets().length != this.getOctets().length) + { + return false; + } + + byte[] b1 = other.getOctets(); + byte[] b2 = this.getOctets(); + + for (int i = 0; i != b1.length; i++) + { + if (b1[i] != b2[i]) + { + return false; + } + } + + return true; + } +} diff --git a/src/org/bouncycastle/asn1/DEROutputStream.java b/src/org/bouncycastle/asn1/DEROutputStream.java new file mode 100644 index 0000000..03e77a2 --- /dev/null +++ b/src/org/bouncycastle/asn1/DEROutputStream.java @@ -0,0 +1,79 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +public class DEROutputStream + extends FilterOutputStream implements DERTags +{ + public DEROutputStream( + OutputStream os) + { + super(os); + } + + private void writeLength( + int length) + throws IOException + { + if (length > 127) + { + int size = 1; + int val = length; + + while ((val >>>= 8) != 0) + { + size++; + } + + write((byte)(size | 0x80)); + + for (int i = (size - 1) * 8; i >= 0; i -= 8) + { + write((byte)(length >> i)); + } + } + else + { + write((byte)length); + } + } + + void writeEncoded( + int tag, + byte[] bytes) + throws IOException + { + write(tag); + writeLength(bytes.length); + write(bytes); + } + + protected void writeNull() + throws IOException + { + write(NULL); + write(0x00); + } + + public void writeObject( + Object obj) + throws IOException + { + if (obj == null) + { + writeNull(); + } + else if (obj instanceof DERObject) + { + ((DERObject)obj).encode(this); + } + else if (obj instanceof DEREncodable) + { + ((DEREncodable)obj).getDERObject().encode(this); + } + else + { + throw new IOException("object not DEREncodable"); + } + } +} diff --git a/src/org/bouncycastle/asn1/DERPrintableString.java b/src/org/bouncycastle/asn1/DERPrintableString.java new file mode 100644 index 0000000..7834edf --- /dev/null +++ b/src/org/bouncycastle/asn1/DERPrintableString.java @@ -0,0 +1,62 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +/** + * DER PrintableString object. + */ +public class DERPrintableString + extends DERObject + implements DERString +{ + String string; + + /** + * basic constructor - byte encoded string. + */ + public DERPrintableString( + byte[] string) + { + try + { + this.string = new String(string, "US-ASCII"); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException("PANIC: " + e); + } + } + + /** + * basic constructor + */ + public DERPrintableString( + String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + public byte[] getOctets() + { + try + { + return string.getBytes("US-ASCII"); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException("PANIC: " + e); + } + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(PRINTABLE_STRING, this.getOctets()); + } +} diff --git a/src/org/bouncycastle/asn1/DERSet.java b/src/org/bouncycastle/asn1/DERSet.java new file mode 100644 index 0000000..2d2caf4 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERSet.java @@ -0,0 +1,24 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +/** + * DER Set with a single object. + */ +public class DERSet + extends DERConstructedSet +{ + /** + * @param sequence the sequence making up the set + */ + public DERSet( + DEREncodable sequence) + { + this.addObject(sequence); + } + + public DERObject getSequence() + { + return (DERObject)this.getObjectAt(0); + } +} diff --git a/src/org/bouncycastle/asn1/DERString.java b/src/org/bouncycastle/asn1/DERString.java new file mode 100644 index 0000000..3143be9 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERString.java @@ -0,0 +1,9 @@ +package org.bouncycastle.asn1; + +/** + * basic interface for DER string objects. + */ +public interface DERString +{ + public String getString(); +} diff --git a/src/org/bouncycastle/asn1/DERT61String.java b/src/org/bouncycastle/asn1/DERT61String.java new file mode 100644 index 0000000..4412efc --- /dev/null +++ b/src/org/bouncycastle/asn1/DERT61String.java @@ -0,0 +1,43 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +/** + * DER T61String (also the teletex string) + */ +public class DERT61String + extends DERObject + implements DERString +{ + String string; + + /** + * basic constructor - with bytes. + */ + public DERT61String( + byte[] string) + { + this.string = new String(string); + } + + /** + * basic constructor - with string. + */ + public DERT61String( + String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(T61_STRING, string.getBytes()); + } +} diff --git a/src/org/bouncycastle/asn1/DERTaggedObject.java b/src/org/bouncycastle/asn1/DERTaggedObject.java new file mode 100644 index 0000000..4ec0310 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERTaggedObject.java @@ -0,0 +1,119 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +/** + * DER TaggedObject - in ASN.1 nottation this is any object proceeded by + * a [n] where n is some number - these are assume to follow the construction + * rules (as with sequences). + */ +public class DERTaggedObject + extends DERObject +{ + int tagNo; + boolean empty = false; + boolean explicit = true; + DEREncodable obj = null; + + /** + * This creates an empty tagged object of tagNo (ie. zero length). + * + * @param tagNo the tag number for this object. + */ + public DERTaggedObject( + int tagNo) + { + this.explicit = true; + this.tagNo = tagNo; + this.empty = true; + } + + /** + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public DERTaggedObject( + int tagNo, + DEREncodable obj) + { + this.explicit = true; + this.tagNo = tagNo; + this.obj = obj; + } + + /** + * @param explicit true if the object is explicitly tagged. + * @param tagNo the tag number for this object. + * @param obj the tagged object. + */ + public DERTaggedObject( + boolean explicit, + int tagNo, + DEREncodable obj) + { + this.explicit = explicit; + this.tagNo = tagNo; + this.obj = obj; + } + + public int getTagNo() + { + return tagNo; + } + + public boolean isExplicit() + { + return explicit; + } + + public boolean isEmpty() + { + return empty; + } + + public DERObject getObject() + { + return obj.getDERObject(); + } + + void encode( + DEROutputStream out) + throws IOException + { + if (!empty) + { + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + DEROutputStream dOut = new DEROutputStream(bOut); + + dOut.writeObject(obj); + dOut.close(); + + byte[] bytes = bOut.toByteArray(); + + if (explicit) + { + out.writeEncoded(CONSTRUCTED | TAGGED | tagNo, bOut.toByteArray()); + } + else + { + // + // need to mark constructed types... + // + if ((bytes[0] & CONSTRUCTED) != 0) + { + bytes[0] = (byte)(CONSTRUCTED | TAGGED | tagNo); + } + else + { + bytes[0] = (byte)(TAGGED | tagNo); + } + + out.write(bytes); + } + } + else + { + out.writeEncoded(CONSTRUCTED | TAGGED | tagNo, new byte[0]); + } + } +} diff --git a/src/org/bouncycastle/asn1/DERTags.java b/src/org/bouncycastle/asn1/DERTags.java new file mode 100644 index 0000000..085b787 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERTags.java @@ -0,0 +1,30 @@ +package org.bouncycastle.asn1; + +public interface DERTags +{ + public static final int BOOLEAN = 0x01; + public static final int INTEGER = 0x02; + public static final int BIT_STRING = 0x03; + public static final int OCTET_STRING = 0x04; + public static final int NULL = 0x05; + public static final int OBJECT_IDENTIFIER = 0x06; + public static final int EXTERNAL = 0x08; + public static final int SEQUENCE = 0x10; + public static final int SEQUENCE_OF = 0x10; // for completeness + public static final int SET = 0x11; + public static final int SET_OF = 0x11; // for completeness + public static final int CONSTRUCTED = 0x20; + public static final int TAGGED = 0x80; + + public static final int NUMERIC_STRING = 0x12; + public static final int PRINTABLE_STRING = 0x13; + public static final int T61_STRING = 0x14; + public static final int VIDEOTEX_STRING = 0x15; + public static final int IA5_STRING = 0x16; + public static final int UTC_TIME = 0x17; + public static final int GENERALIZED_TIME = 0x18; + public static final int GRAPHIC_STRING = 0x19; + public static final int VISIBLE_STRING = 0x1a; + public static final int GENERAL_STRING = 0x1b; + public static final int BMP_STRING = 0x1e; +} diff --git a/src/org/bouncycastle/asn1/DERUTCTime.java b/src/org/bouncycastle/asn1/DERUTCTime.java new file mode 100644 index 0000000..061bf54 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERUTCTime.java @@ -0,0 +1,79 @@ +package org.bouncycastle.asn1; + +import java.io.*; +import java.util.*; +import java.io.*; + +/** + * UTC time object. + */ +public class DERUTCTime + extends DERObject +{ + String time; + + /** + * The correct format for this is YYMMDDHHMMSSZ (it used to be that seconds were + * never encoded. When you're creating one of these objects from scratch, that's + * what you want to use, otherwise we'll try to deal with whatever gets read from + * the input stream... (this is why the input format is different from the getTime() + * method output). + *

+ * You can generate a Java date string in the right format by using: + *

+     *      dateF = new SimpleDateFormat("yyMMddHHmmss");
+     *      tz = new SimpleTimeZone(0, "Z");
+     *     
+     *      dateF.setTimeZone(tz);
+     *
+     *      utcTime = new DERUTCTime(dateF.format(new Date()) + "Z");
+     * 
+ * + * @param time the time string. + */ + public DERUTCTime( + String time) + { + this.time = time; + } + + /** + * return the time - always in the form of + * YYMMDDhhmmssGMT(+hh:mm|-hh:mm). + *

+ * Normally in a certificate we would expect "Z" rather than "GMT", + * however adding the "GMT" means we can just use: + *

+     *     dateF = new SimpleDateFormat("yyMMddHHmmssz");
+     * 
+ * To read in the time and get a date which is compatible with our local + * time zone. + */ + public String getTime() + { + // + // standardise the format. + // + if (time.length() == 11) + { + return time.substring(0, 10) + "00GMT+00:00"; + } + else if (time.length() == 13) + { + return time.substring(0, 12) + "GMT+00:00"; + } + else if (time.length() == 17) + { + return time.substring(0, 12) + "GMT" + time.substring(12, 15) + ":" + time.substring(15, 17); + } + + return time; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(UTC_TIME, time.getBytes()); + } +} diff --git a/src/org/bouncycastle/asn1/DERUnknownTag.java b/src/org/bouncycastle/asn1/DERUnknownTag.java new file mode 100644 index 0000000..c5f46f9 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERUnknownTag.java @@ -0,0 +1,42 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +/** + * We insert one of these when we find a tag we don't recognise. + */ +public class DERUnknownTag + extends DERObject +{ + int tag; + byte[] data; + + /** + * @param tag the tag value. + * @param data the octets making up the time. + */ + public DERUnknownTag( + int tag, + byte[] data) + { + this.tag = tag; + this.data = data; + } + + public int getTag() + { + return tag; + } + + public byte[] getData() + { + return data; + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(tag, data); + } +} diff --git a/src/org/bouncycastle/asn1/DERVisibleString.java b/src/org/bouncycastle/asn1/DERVisibleString.java new file mode 100644 index 0000000..f2071d3 --- /dev/null +++ b/src/org/bouncycastle/asn1/DERVisibleString.java @@ -0,0 +1,62 @@ +package org.bouncycastle.asn1; + +import java.io.*; + +/** + * DER VisibleString object. + */ +public class DERVisibleString + extends DERObject + implements DERString +{ + String string; + + /** + * basic constructor - byte encoded string. + */ + public DERVisibleString( + byte[] string) + { + try + { + this.string = new String(string, "US-ASCII"); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException("PANIC: " + e); + } + } + + /** + * basic constructor + */ + public DERVisibleString( + String string) + { + this.string = string; + } + + public String getString() + { + return string; + } + + public byte[] getOctets() + { + try + { + return string.getBytes("US-ASCII"); + } + catch(UnsupportedEncodingException e) + { + throw new RuntimeException("PANIC: " + e); + } + } + + void encode( + DEROutputStream out) + throws IOException + { + out.writeEncoded(VISIBLE_STRING, this.getOctets()); + } +} diff --git a/src/org/bouncycastle/asn1/OIDTokenizer.java b/src/org/bouncycastle/asn1/OIDTokenizer.java new file mode 100644 index 0000000..5467944 --- /dev/null +++ b/src/org/bouncycastle/asn1/OIDTokenizer.java @@ -0,0 +1,48 @@ +package org.bouncycastle.asn1; + +/** + * class for breaking up an OID into it's component tokens, ala + * java.util.StringTokenizer. We need this class as some of the + * lightweight Java environment don't support classes like + * StringTokenizer. + */ +public class OIDTokenizer +{ + private String oid; + private int index; + + public OIDTokenizer( + String oid) + { + this.oid = oid; + this.index = 0; + } + + public boolean hasMoreTokens() + { + return (index != -1); + } + + public String nextToken() + { + if (index == -1) + { + return null; + } + + String token; + int end = oid.indexOf('.', index); + + if (end == -1) + { + token = oid.substring(index); + index = -1; + return token; + } + + token = oid.substring(index, end); + + index = end + 1; + return token; + } +} diff --git a/src/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java b/src/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java new file mode 100644 index 0000000..f471332 --- /dev/null +++ b/src/org/bouncycastle/asn1/pkcs/PKCSObjectIdentifiers.java @@ -0,0 +1,98 @@ +package org.bouncycastle.asn1.pkcs; + +import org.bouncycastle.asn1.DERObjectIdentifier; + +public interface PKCSObjectIdentifiers +{ + // + // pkcs-1 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 1 } + // + static final String pkcs_1 = "1.2.840.113549.1.1"; + static final DERObjectIdentifier rsaEncryption = new DERObjectIdentifier(pkcs_1 + ".1"); + static final DERObjectIdentifier md2WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".2"); + static final DERObjectIdentifier md4WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".3"); + static final DERObjectIdentifier md5WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".4"); + static final DERObjectIdentifier sha1WithRSAEncryption = new DERObjectIdentifier(pkcs_1 + ".5"); + static final DERObjectIdentifier srsaOAEPEncryptionSET = new DERObjectIdentifier(pkcs_1 + ".6"); + + // + // pkcs-3 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 3 } + // + static final String pkcs_3 = "1.2.840.113549.1.3"; + static final DERObjectIdentifier dhKeyAgreement = new DERObjectIdentifier(pkcs_3 + ".1"); + + // + // pkcs-5 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 5 } + // + static final String pkcs_5 = "1.2.840.113549.1.5"; + + static final DERObjectIdentifier id_PBES2 = new DERObjectIdentifier(pkcs_5 + ".13"); + + static final DERObjectIdentifier id_PBKDF2 = new DERObjectIdentifier(pkcs_5 + ".12"); + + // + // encryptionAlgorithm OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) 3 } + // + static final String encryptionAlgorithm = "1.2.840.113549.3"; + + static final DERObjectIdentifier des_EDE3_CBC = new DERObjectIdentifier(encryptionAlgorithm + ".7"); + static final DERObjectIdentifier RC2_CBC = new DERObjectIdentifier(encryptionAlgorithm + ".2"); + + // + // object identifiers for digests + // + + // + // md2 OBJECT IDENTIFIER ::= + // {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 2} + // + static final DERObjectIdentifier md2 = new DERObjectIdentifier("1.2.840.113549.2.2"); + + // + // md5 OBJECT IDENTIFIER ::= + // {iso(1) member-body(2) US(840) rsadsi(113549) digestAlgorithm(2) 5} + // + static final DERObjectIdentifier md5 = new DERObjectIdentifier("1.2.840.113549.2.5"); + + // + // pkcs-7 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 7 } + // + static final String pkcs_7 = "1.2.840.113549.1.7"; + static final DERObjectIdentifier data = new DERObjectIdentifier(pkcs_7 + ".1"); + static final DERObjectIdentifier signedData = new DERObjectIdentifier(pkcs_7 + ".2"); + static final DERObjectIdentifier envelopedData = new DERObjectIdentifier(pkcs_7 + ".3"); + static final DERObjectIdentifier signedAndEnvelopedData = new DERObjectIdentifier(pkcs_7 + ".4"); + static final DERObjectIdentifier digestedData = new DERObjectIdentifier(pkcs_7 + ".5"); + static final DERObjectIdentifier encryptedData = new DERObjectIdentifier(pkcs_7 + ".6"); + + // + // pkcs-9 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 9 } + // + static final String pkcs_9 = "1.2.840.113549.1.9"; + + static final DERObjectIdentifier pkcs_9_at_emailAddress = new DERObjectIdentifier(pkcs_9 + ".1"); + static final DERObjectIdentifier pkcs_9_at_friendlyName = new DERObjectIdentifier(pkcs_9 + ".20"); + static final DERObjectIdentifier pkcs_9_at_localKeyId = new DERObjectIdentifier(pkcs_9 + ".21"); + static final DERObjectIdentifier x509certType = new DERObjectIdentifier(pkcs_9 + ".22.1"); + + // + // pkcs-12 OBJECT IDENTIFIER ::= { + // iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) 12 } + // + static final String pkcs_12 = "1.2.840.113549.1.12"; + static final String bagtypes = pkcs_12 + ".10.1"; + + static final DERObjectIdentifier keyBag = new DERObjectIdentifier(bagtypes + ".1"); + static final DERObjectIdentifier pkcs8ShroudedKeyBag = new DERObjectIdentifier(bagtypes + ".2"); + static final DERObjectIdentifier certBag = new DERObjectIdentifier(bagtypes + ".3"); + static final DERObjectIdentifier crlBag = new DERObjectIdentifier(bagtypes + ".4"); + static final DERObjectIdentifier secretBag = new DERObjectIdentifier(bagtypes + ".5"); + static final DERObjectIdentifier safeContentsBag = new DERObjectIdentifier(bagtypes + ".6"); +} + diff --git a/src/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java b/src/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java new file mode 100644 index 0000000..cad4c82 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/AlgorithmIdentifier.java @@ -0,0 +1,136 @@ +package org.bouncycastle.asn1.x509; + +import java.io.*; +import java.util.Enumeration; + +import org.bouncycastle.asn1.*; + +public class AlgorithmIdentifier + implements DEREncodable +{ + private DERObjectIdentifier objectId; + private DERObject parameters; + private boolean parametersDefined = false; + + public AlgorithmIdentifier( + DERObjectIdentifier objectId) + { + this.objectId = objectId; + } + + public AlgorithmIdentifier( + DERObjectIdentifier objectId, + DERObject parameters) + { + parametersDefined = true; + + this.objectId = objectId; + this.parameters = parameters; + } + + public AlgorithmIdentifier( + DERConstructedSequence obj) + { + objectId = (DERObjectIdentifier)obj.getObjectAt(0); + + if (obj.getSize() == 2) + { + parametersDefined = true; + parameters = (DERObject)obj.getObjectAt(1); + } + else + { + parameters = null; + } + } + + public DERObjectIdentifier getObjectId() + { + return objectId; + } + + public DERObject getParameters() + { + return parameters; + } + + /** + *
+     *      AlgorithmIdentifier ::= SEQUENCE {
+     *                            algorithm OBJECT IDENTIFIER,
+     *                            parameters ANY DEFINED BY algorithm OPTIONAL }
+     * 
+ */ + public DERObject getDERObject() + { + DERConstructedSequence seq = new DERConstructedSequence(); + + seq.addObject(objectId); + + if (parametersDefined) + { + seq.addObject(parameters); + } + + return seq; + } + + public boolean equals( + Object o) + { + if ((o == null) || !(o instanceof AlgorithmIdentifier)) + { + return false; + } + + AlgorithmIdentifier other = (AlgorithmIdentifier)o; + + if (!this.getObjectId().equals(other.getObjectId())) + { + return false; + } + + if (this.getParameters() == null && other.getParameters() == null) + { + return true; + } + + if (this.getParameters() == null || other.getParameters() == null) + { + return false; + } + + ByteArrayOutputStream b1Out = new ByteArrayOutputStream(); + ByteArrayOutputStream b2Out = new ByteArrayOutputStream(); + DEROutputStream d1Out = new DEROutputStream(b1Out); + DEROutputStream d2Out = new DEROutputStream(b2Out); + + try + { + d1Out.writeObject(this.getParameters()); + d2Out.writeObject(other.getParameters()); + + byte[] b1 = b1Out.toByteArray(); + byte[] b2 = b2Out.toByteArray(); + + if (b1.length != b2.length) + { + return false; + } + + for (int i = 0; i != b1.length; i++) + { + if (b1[i] != b2[i]) + { + return false; + } + } + } + catch (Exception e) + { + return false; + } + + return true; + } +} diff --git a/src/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java b/src/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java new file mode 100644 index 0000000..9285c96 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/AuthorityKeyIdentifier.java @@ -0,0 +1,172 @@ +package org.bouncycastle.asn1.x509; + +import java.math.BigInteger; + +import java.util.Enumeration; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.asn1.*; + +/** + *
+ * id-ce-authorityKeyIdentifier OBJECT IDENTIFIER ::=  { id-ce 35 }
+ *
+ *   AuthorityKeyIdentifier ::= SEQUENCE {
+ *      keyIdentifier             [0] IMPLICIT KeyIdentifier           OPTIONAL,
+ *      authorityCertIssuer       [1] IMPLICIT GeneralNames            OPTIONAL,
+ *      authorityCertSerialNumber [2] IMPLICIT CertificateSerialNumber OPTIONAL  }
+ *
+ *   KeyIdentifier ::= OCTET STRING
+ * 
+ * + */ +public class AuthorityKeyIdentifier + implements DEREncodable +{ + DEROctetString keyidentifier=null; + GeneralNames certissuer=null; + DERInteger certserno=null; + + public AuthorityKeyIdentifier( + DERConstructedSequence seq) + { + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + DERTaggedObject o = (DERTaggedObject)e.nextElement(); + + switch (o.getTagNo()) + { + case 0: + this.keyidentifier= (DEROctetString)o.getObject(); + break; + + case 1: + if (o.getObject() instanceof DERConstructedSequence) + { + this.certissuer = new GeneralNames((DERConstructedSequence)o.getObject()); + } + else + { + // as it's implicitly tagged we can loose the"sequence" + // if there is only one object. + // + DERConstructedSequence s = new DERConstructedSequence(); + + s.addObject(o.getObject()); + + this.certissuer = new GeneralNames(s); + } + break; + case 2: + // + // implicit tagging again... + // + DEROctetString oct = (DEROctetString)o.getObject(); + + this.certserno = new DERInteger(new BigInteger(oct.getOctets())); + break; + default: + throw new IllegalArgumentException("illegal tag"); + } + } + } + + /** + * + * Calulates the keyidentifier using a SHA1 hash over the BIT STRING + * from SubjectPublicKeyInfo as defined in RFC2459. + * + * Example of making a AuthorityKeyIdentifier: + *
+     *   SubjectPublicKeyInfo apki = new SubjectPublicKeyInfo((DERConstructedSequence)new DERInputStream(
+     *       new ByteArrayInputStream(publicKey.getEncoded())).readObject());
+     *   AuthorityKeyIdentifier aki = new AuthorityKeyIdentifier(apki);
+     * 
+ * + **/ + public AuthorityKeyIdentifier( + SubjectPublicKeyInfo spki) + { + Digest digest = new SHA1Digest(); + byte[] resBuf = new byte[digest.getDigestSize()]; + + DERBitString derpk = new DERBitString(spki.getPublicKey()); + byte[] bytes = derpk.getBytes(); + digest.update(bytes, 0, bytes.length); + digest.doFinal(resBuf, 0); + this.keyidentifier=new DEROctetString(resBuf); + } + + /** + * create an AuthorityKeyIdentifier with the GeneralNames tag and + * the serial number provided as well. + */ + public AuthorityKeyIdentifier( + SubjectPublicKeyInfo spki, + GeneralNames name, + BigInteger serialNumber) + { + Digest digest = new SHA1Digest(); + byte[] resBuf = new byte[digest.getDigestSize()]; + + DERBitString derpk = new DERBitString(spki.getPublicKey()); + byte[] bytes = derpk.getBytes(); + digest.update(bytes, 0, bytes.length); + digest.doFinal(resBuf, 0); + + this.keyidentifier = new DEROctetString(resBuf); + this.certissuer = name; + this.certserno = new DERInteger(serialNumber); + } + + public byte[] getKeyIdentifier() + { + if (keyidentifier != null) + { + return keyidentifier.getOctets(); + } + + return null; + } + + /** + *
+     *   AuthorityKeyIdentifier ::= SEQUENCE {
+     *      keyIdentifier             [0] IMPLICIT KeyIdentifier           OPTIONAL,
+     *      authorityCertIssuer       [1] IMPLICIT GeneralNames            OPTIONAL,
+     *      authorityCertSerialNumber [2] IMPLICIT CertificateSerialNumber OPTIONAL  }
+     *
+     *   KeyIdentifier ::= OCTET STRING
+     * 
+ */ + public DERObject getDERObject() + { + DERConstructedSequence seq = new DERConstructedSequence(); + + if (keyidentifier != null) + { + seq.addObject(new DERTaggedObject(false, 0, keyidentifier)); + } + + if (certissuer != null) + { + seq.addObject(new DERTaggedObject(false, 1, certissuer)); + } + + if (certserno != null) + { + seq.addObject(new DERTaggedObject(false, 2, certserno)); + } + + + return seq; + } + + public String toString() + { + return ("AuthorityKeyIdentifier: KeyID(" + this.keyidentifier.getOctets() + ")"); + } +} diff --git a/src/org/bouncycastle/asn1/x509/BasicConstraints.java b/src/org/bouncycastle/asn1/x509/BasicConstraints.java new file mode 100644 index 0000000..ec84915 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/BasicConstraints.java @@ -0,0 +1,79 @@ +package org.bouncycastle.asn1.x509; + +import java.math.BigInteger; + +import org.bouncycastle.asn1.*; + +public class BasicConstraints + implements DEREncodable +{ + DERBoolean cA = new DERBoolean(false); + DERInteger pathLenConstraint = null; + + public BasicConstraints( + DERConstructedSequence seq) + { + if (seq.getSize() != 0) + { + this.cA = (DERBoolean)seq.getObjectAt(0); + this.pathLenConstraint = (DERInteger)seq.getObjectAt(1); + } + } + + public BasicConstraints( + boolean cA, + int pathLenConstraint) + { + this.cA = new DERBoolean(cA); + this.pathLenConstraint = new DERInteger(pathLenConstraint); + } + + public BasicConstraints( + boolean cA) + { + this.cA = new DERBoolean(cA); + this.pathLenConstraint = null; + } + + public boolean isCA() + { + return cA.isTrue(); + } + + public BigInteger getPathLenConstraint() + { + if (pathLenConstraint != null) + { + return pathLenConstraint.getValue(); + } + + return null; + } + + /** + *
+     * BasicConstraints := SEQUENCE {
+     *    cA                  BOOLEAN DEFAULT FALSE,
+     *    pathLenConstraint   INTEGER (0..MAX) OPTIONAL
+     * }
+     * 
+ */ + public DERObject getDERObject() + { + DERConstructedSequence seq = new DERConstructedSequence(); + + seq.addObject(cA); + + if (pathLenConstraint != null) + { + seq.addObject(pathLenConstraint); + } + + return seq; + } + + public String toString() + { + return "BasicConstraints: isCa(" + this.isCA() + "), pathLenConstraint = " + pathLenConstraint.getValue(); + } +} diff --git a/src/org/bouncycastle/asn1/x509/CRLDistPoint.java b/src/org/bouncycastle/asn1/x509/CRLDistPoint.java new file mode 100644 index 0000000..780ab16 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/CRLDistPoint.java @@ -0,0 +1,30 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; + +public class CRLDistPoint + implements DEREncodable +{ + DERConstructedSequence seq = null; + + public CRLDistPoint( + DistributionPoint[] points) + { + seq = new DERConstructedSequence(); + + for (int i = 0; i != points.length; i++) + { + seq.addObject(points[i]); + } + } + + /** + *
+     * CRLDistPoint ::= SEQUENCE SIZE {1..MAX} OF DistributionPoint
+     * 
+ */ + public DERObject getDERObject() + { + return seq; + } +} diff --git a/src/org/bouncycastle/asn1/x509/CRLNumber.java b/src/org/bouncycastle/asn1/x509/CRLNumber.java new file mode 100644 index 0000000..bf9b3c9 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/CRLNumber.java @@ -0,0 +1,26 @@ +package org.bouncycastle.asn1.x509; + +import java.math.BigInteger; + +import org.bouncycastle.asn1.*; + +/** + *
+ * CRLNumber::= INTEGER(0..MAX)
+ * 
+ */ +public class CRLNumber + extends DERInteger +{ + + public CRLNumber( + BigInteger number) + { + super(number); + } + + public BigInteger getCRLNumber() + { + return getPositiveValue(); + } +} diff --git a/src/org/bouncycastle/asn1/x509/CertificateList.java b/src/org/bouncycastle/asn1/x509/CertificateList.java new file mode 100644 index 0000000..48703d1 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/CertificateList.java @@ -0,0 +1,101 @@ + +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.*; + +/** + * PKIX RFC-2459 + * + * The X.509 v2 CRL syntax is as follows. For signature calculation, + * the data that is to be signed is ASN.1 DER encoded. + * + *
+ * CertificateList  ::=  SEQUENCE  {
+ *      tbsCertList          TBSCertList,
+ *      signatureAlgorithm   AlgorithmIdentifier,
+ *      signatureValue       BIT STRING  }
+ * 
+ */ + +public class CertificateList + implements DEREncodable +{ + DERConstructedSequence seq; + + TBSCertList tbsCertList; + AlgorithmIdentifier sigAlgId; + DERBitString sig; + + public CertificateList( + DERConstructedSequence seq) + { + this.seq = seq; + + if ( seq.getObjectAt(0) instanceof TBSCertList ) + { + tbsCertList = (TBSCertList)seq.getObjectAt(0); + } + else + { + tbsCertList = new TBSCertList((DERConstructedSequence)seq.getObjectAt(0)); + } + + if ( seq.getObjectAt(1) instanceof AlgorithmIdentifier ) + { + sigAlgId = (AlgorithmIdentifier)seq.getObjectAt(1); + } + else + { + sigAlgId = new AlgorithmIdentifier((DERConstructedSequence)seq.getObjectAt(1)); + } + + sig = (DERBitString)seq.getObjectAt(2); + } + + public TBSCertList getTBSCertList() + { + return tbsCertList; + } + + public TBSCertList.CRLEntry[] getRevokedCertificates() + { + return tbsCertList.getRevokedCertificates(); + } + + public AlgorithmIdentifier getSignatureAlgorithm() + { + return sigAlgId; + } + + public DERBitString getSignature() + { + return sig; + } + + public int getVersion() + { + return tbsCertList.getVersion(); + } + + public X509Name getIssuer() + { + return tbsCertList.getIssuer(); + } + + public DERUTCTime getThisUpdate() + { + return tbsCertList.getThisUpdate(); + } + + public DERUTCTime getNextUpdate() + { + return tbsCertList.getNextUpdate(); + } + + public DERObject getDERObject() + { + return seq; + } +} + diff --git a/src/org/bouncycastle/asn1/x509/DSAParameter.java b/src/org/bouncycastle/asn1/x509/DSAParameter.java new file mode 100644 index 0000000..b6234bb --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/DSAParameter.java @@ -0,0 +1,58 @@ +package org.bouncycastle.asn1.x509; + +import java.math.*; +import java.util.*; + +import org.bouncycastle.asn1.*; + +public class DSAParameter + implements DEREncodable +{ + DERInteger p, q, g; + + public DSAParameter( + BigInteger p, + BigInteger q, + BigInteger g) + { + this.p = new DERInteger(p); + this.q = new DERInteger(q); + this.g = new DERInteger(g); + } + + public DSAParameter( + DERConstructedSequence seq) + { + Enumeration e = seq.getObjects(); + + p = (DERInteger)e.nextElement(); + q = (DERInteger)e.nextElement(); + g = (DERInteger)e.nextElement(); + } + + public BigInteger getP() + { + return p.getPositiveValue(); + } + + public BigInteger getQ() + { + return q.getPositiveValue(); + } + + public BigInteger getG() + { + return g.getPositiveValue(); + } + + public DERObject getDERObject() + { + DERConstructedSequence seq = new DERConstructedSequence(); + + seq.addObject(p); + seq.addObject(q); + seq.addObject(g); + + return seq; + } +} diff --git a/src/org/bouncycastle/asn1/x509/DigestInfo.java b/src/org/bouncycastle/asn1/x509/DigestInfo.java new file mode 100644 index 0000000..b15d37e --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/DigestInfo.java @@ -0,0 +1,57 @@ +package org.bouncycastle.asn1.x509; + +import java.util.Enumeration; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +/** + *
+ * DigestInfo::=SEQUENCE{
+ *          digestAlgorithm  AlgorithmIdentifier,
+ *          digest OCTET STRING }
+ * 
+ */ +public class DigestInfo + implements PKCSObjectIdentifiers, DEREncodable +{ + private byte[] digest; + private AlgorithmIdentifier algId; + + public DigestInfo( + AlgorithmIdentifier algId, + byte[] digest) + { + this.digest = digest; + this.algId = algId; + } + + public DigestInfo( + DERConstructedSequence seq) + { + Enumeration e = seq.getObjects(); + + algId = new AlgorithmIdentifier((DERConstructedSequence)e.nextElement()); + digest = ((DEROctetString)e.nextElement()).getOctets(); + } + + public AlgorithmIdentifier getAlgorithmId() + { + return algId; + } + + public byte[] getDigest() + { + return digest; + } + + public DERObject getDERObject() + { + DERConstructedSequence seq = new DERConstructedSequence(); + + seq.addObject(algId); + seq.addObject(new DEROctetString(digest)); + + return seq; + } +} diff --git a/src/org/bouncycastle/asn1/x509/DistributionPoint.java b/src/org/bouncycastle/asn1/x509/DistributionPoint.java new file mode 100644 index 0000000..cb64d68 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/DistributionPoint.java @@ -0,0 +1,46 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; + +public class DistributionPoint + implements DEREncodable +{ + DERConstructedSequence seq = null; + + public DistributionPoint( + DistributionPointName distributionPoint, + ReasonFlags reasons, + GeneralNames cRLIssuer) + { + seq = new DERConstructedSequence(); + + if (distributionPoint != null) + { + seq.addObject(new DERTaggedObject(0, distributionPoint)); + } + + if (reasons != null) + { + seq.addObject(new DERTaggedObject(1, reasons)); + } + + if (cRLIssuer != null) + { + seq.addObject(new DERTaggedObject(2, cRLIssuer)); + } + } + + /** + *
+     * DistributionPoint ::= SEQUENCE {
+     *      distributionPoint [0] DistributionPointName OPTIONAL,
+     *      reasons           [1] ReasonFlags OPTIONAL,
+     *      cRLIssuer         [2] GeneralNames OPTIONAL
+     * }
+     * 
+ */ + public DERObject getDERObject() + { + return seq; + } +} diff --git a/src/org/bouncycastle/asn1/x509/DistributionPointName.java b/src/org/bouncycastle/asn1/x509/DistributionPointName.java new file mode 100644 index 0000000..bac341e --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/DistributionPointName.java @@ -0,0 +1,34 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; + +public class DistributionPointName + implements DEREncodable +{ + DEREncodable name; + int type; + + public static final int FULL_NAME = 0; + public static final int NAME_RELATIVE_TO_CRL_ISSUER = 1; + + public DistributionPointName( + int type, + DEREncodable name) + { + this.type = type; + this.name = name; + } + + /** + *
+     * DistributionPointName ::= CHOICE {
+     *     fullName                 [0] GeneralNames,
+     *     nameRelativeToCRLIssuer  [1] RelativeDistinguishedName
+     * }
+     * 
+ */ + public DERObject getDERObject() + { + return new DERTaggedObject(false, type, name); + } +} diff --git a/src/org/bouncycastle/asn1/x509/GeneralName.java b/src/org/bouncycastle/asn1/x509/GeneralName.java new file mode 100644 index 0000000..41c2578 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/GeneralName.java @@ -0,0 +1,78 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; + +/** + *
+ * GeneralName ::= CHOICE {
+ *      otherName                       [0]     OtherName,
+ *      rfc822Name                      [1]     IA5String,
+ *      dNSName                         [2]     IA5String,
+ *      x400Address                     [3]     ORAddress,
+ *      directoryName                   [4]     Name,
+ *      ediPartyName                    [5]     EDIPartyName,
+ *      uniformResourceIdentifier       [6]     IA5String,
+ *      iPAddress                       [7]     OCTET STRING,
+ *      registeredID                    [8]     OBJECT IDENTIFIER}
+ *
+ * OtherName ::= SEQUENCE {
+ *      type-id    OBJECT IDENTIFIER,
+ *      value      [0] EXPLICIT ANY DEFINED BY type-id }
+ *
+ * EDIPartyName ::= SEQUENCE {
+ *      nameAssigner            [0]     DirectoryString OPTIONAL,
+ *      partyName               [1]     DirectoryString }
+ * 
+ */ +public class GeneralName + implements DEREncodable +{ + DEREncodable obj; + int tag; + + public GeneralName( + X509Name directoryName) + { + this.obj = directoryName; + this.tag = 4; + } + + /** + * When the subjectAltName extension contains an Internet mail address, + * the address MUST be included as an rfc822Name. The format of an + * rfc822Name is an "addr-spec" as defined in RFC 822 [RFC 822]. + * + * When the subjectAltName extension contains a domain name service + * label, the domain name MUST be stored in the dNSName (an IA5String). + * The name MUST be in the "preferred name syntax," as specified by RFC + * 1034 [RFC 1034]. + * + * When the subjectAltName extension contains a URI, the name MUST be + * stored in the uniformResourceIdentifier (an IA5String). The name MUST + * be a non-relative URL, and MUST follow the URL syntax and encoding + * rules specified in [RFC 1738]. The name must include both a scheme + * (e.g., "http" or "ftp") and a scheme-specific-part. The scheme- + * specific-part must include a fully qualified domain name or IP + * address as the host. + * + * When the subjectAltName extension contains a iPAddress, the address + * MUST be stored in the octet string in "network byte order," as + * specified in RFC 791 [RFC 791]. The least significant bit (LSB) of + * each octet is the LSB of the corresponding byte in the network + * address. For IP Version 4, as specified in RFC 791, the octet string + * MUST contain exactly four octets. For IP Version 6, as specified in + * RFC 1883, the octet string MUST contain exactly sixteen octets [RFC + * 1883]. + */ + public GeneralName( + DERObject name, int tag) + { + this.obj = name; + this.tag = tag; + } + + public DERObject getDERObject() + { + return new DERTaggedObject(false, tag, obj); + } +} diff --git a/src/org/bouncycastle/asn1/x509/GeneralNames.java b/src/org/bouncycastle/asn1/x509/GeneralNames.java new file mode 100644 index 0000000..e13ffb0 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/GeneralNames.java @@ -0,0 +1,25 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; + +public class GeneralNames + implements DEREncodable +{ + DERConstructedSequence seq; + + public GeneralNames( + DERConstructedSequence seq) + { + this.seq = seq; + } + + /** + *
+     * GeneralNames ::= SEQUENCE SIZE {1..MAX} OF GeneralName
+     * 
+ */ + public DERObject getDERObject() + { + return seq; + } +} diff --git a/src/org/bouncycastle/asn1/x509/KeyUsage.java b/src/org/bouncycastle/asn1/x509/KeyUsage.java new file mode 100644 index 0000000..e4ed284 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/KeyUsage.java @@ -0,0 +1,68 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; + +/** + *
+ *    id-ce-keyUsage OBJECT IDENTIFIER ::=  { id-ce 15 }
+ *
+ *    KeyUsage ::= BIT STRING {
+ *         digitalSignature        (0),
+ *         nonRepudiation          (1),
+ *         keyEncipherment         (2),
+ *         dataEncipherment        (3),
+ *         keyAgreement            (4),
+ *         keyCertSign             (5),
+ *         cRLSign                 (6),
+ *         encipherOnly            (7),
+ *         decipherOnly            (8) }
+ * 
+ */ +public class KeyUsage + extends DERBitString +{ + public static final int digitalSignature = (1 << 7); + public static final int nonRepudiation = (1 << 6); + public static final int keyEncipherment = (1 << 5); + public static final int dataEncipherment = (1 << 4); + public static final int keyAgreement = (1 << 3); + public static final int keyCertSign = (1 << 2); + public static final int cRLSign = (1 << 1); + public static final int encipherOnly = (1 << 0); + public static final int decipherOnly = (1 << 15); + + static private byte[] getUsageBytes( + int usage) + { + byte[] usageBytes = new byte[2]; + + usageBytes[0] = (byte)(usage & 0xFF); + usageBytes[1] = (byte)((usage >> 8) & 0xFF); + + return usageBytes; + } + + /** + * Basic constructor. + * + * @param usage - the bitwise OR of the Key Usage flags giving the + * allowed uses for the key. + * e.g. (X509KeyUsage.keyEncipherment | X509KeyUsage.dataEncipherment) + */ + public KeyUsage( + int usage) + { + super(getUsageBytes(usage), 7); + } + + public KeyUsage( + DERBitString usage) + { + super(usage.getBytes(), usage.getPadBits()); + } + + public String toString() + { + return "KeyUsage: 0x" + Integer.toHexString(data[0] & 0xff); + } +} diff --git a/src/org/bouncycastle/asn1/x509/RSAPublicKeyStructure.java b/src/org/bouncycastle/asn1/x509/RSAPublicKeyStructure.java new file mode 100644 index 0000000..b792fe1 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/RSAPublicKeyStructure.java @@ -0,0 +1,60 @@ +package org.bouncycastle.asn1.x509; + +import java.util.Enumeration; +import java.math.BigInteger; + +import org.bouncycastle.asn1.*; + +public class RSAPublicKeyStructure + implements DEREncodable +{ + private BigInteger modulus; + private BigInteger publicExponent; + + public RSAPublicKeyStructure( + BigInteger modulus, + BigInteger publicExponent) + { + this.modulus = modulus; + this.publicExponent = publicExponent; + } + + public RSAPublicKeyStructure( + DERConstructedSequence seq) + { + Enumeration e = seq.getObjects(); + + modulus = ((DERInteger)e.nextElement()).getValue(); + publicExponent = ((DERInteger)e.nextElement()).getValue(); + } + + public BigInteger getModulus() + { + return modulus; + } + + public BigInteger getPublicExponent() + { + return publicExponent; + } + + /** + * This outputs the key in PKCS1v2 format. + *
+     *      RSAPublicKey ::= SEQUENCE {
+     *                          modulus INTEGER, -- n
+     *                          publicExponent INTEGER, -- e
+     *                      }
+     * 
+ *

+ */ + public DERObject getDERObject() + { + DERConstructedSequence seq = new DERConstructedSequence(); + + seq.addObject(new DERInteger(getModulus())); + seq.addObject(new DERInteger(getPublicExponent())); + + return seq; + } +} diff --git a/src/org/bouncycastle/asn1/x509/ReasonFlags.java b/src/org/bouncycastle/asn1/x509/ReasonFlags.java new file mode 100644 index 0000000..568cf1f --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/ReasonFlags.java @@ -0,0 +1,33 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; + +public class ReasonFlags + extends DERBitString +{ + public static final int KEY_COMPROMISE = 1; + public static final int CA_COMPROMISE = (1 << 2); + public static final int AFFILIATION_CHANGED = (1 << 3); + public static final int SUPERSEDED = (1 << 4); + public static final int CESSATION_OF_OPERATION = (1 << 5); + public static final int CERTIFICATE_HOLD = (1 << 6); + + /** + *

+     * ReasonFlags ::= BIT STRING {
+     *    unused(0),
+     *    keyCompromise(1),
+     *    cACompromise(2),
+     *    affiliationChanged(3),
+     *    superseded(4),
+     *    cessationOfOperation(5),
+     *    certficateHold(6)
+     * }
+     * 
+ */ + public ReasonFlags( + int reasons) + { + super((byte)reasons, 1); + } +} diff --git a/src/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java b/src/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java new file mode 100644 index 0000000..0dd2b27 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/SubjectKeyIdentifier.java @@ -0,0 +1,64 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.crypto.Digest; +import org.bouncycastle.crypto.digests.SHA1Digest; +import org.bouncycastle.asn1.*; + +/** + *
+ * SubjectKeyIdentifier::= OCTET STRING
+ * 
+ */ +public class SubjectKeyIdentifier + implements DEREncodable +{ + private byte[] keyidentifier; + + public SubjectKeyIdentifier( + byte[] keyid) + { + this.keyidentifier=keyid; + } + + public SubjectKeyIdentifier( + DEROctetString keyid) + { + this.keyidentifier=keyid.getOctets(); + + } + + /** + * + * Calulates the keyidentifier using a SHA1 hash over the BIT STRING + * from SubjectPublicKeyInfo as defined in RFC2459. + * + **/ + public SubjectKeyIdentifier( + SubjectPublicKeyInfo spki) + { + Digest digest = new SHA1Digest(); + byte[] resBuf = new byte[digest.getDigestSize()]; + + DERBitString derpk = new DERBitString(spki.getPublicKey()); + byte[] bytes = derpk.getBytes(); + digest.update(bytes, 0, bytes.length); + digest.doFinal(resBuf, 0); + this.keyidentifier=resBuf; + } + + public byte[] getKeyIdentifier() + { + return keyidentifier; + } + + /** + *
+     * SubjectKeyIdentifier := OCTET STRING
+     * 
+ */ + public DERObject getDERObject() + { + DEROctetString oct = new DEROctetString(keyidentifier); + return oct; + } +} diff --git a/src/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java b/src/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java new file mode 100644 index 0000000..2e65881 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/SubjectPublicKeyInfo.java @@ -0,0 +1,78 @@ +package org.bouncycastle.asn1.x509; + +import java.io.*; +import java.util.Enumeration; +import java.math.BigInteger; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; + +/** + * The object that contains the public key stored in a certficate. + *

+ * The getEncoded() method in the public keys in the JCE produces a DER + * encoded one of these. + */ +public class SubjectPublicKeyInfo + implements DEREncodable +{ + private AlgorithmIdentifier algId; + private DERObject pubKey; + + public SubjectPublicKeyInfo( + AlgorithmIdentifier algId, + DERObject publicKey) + { + this.pubKey = publicKey; + this.algId = algId; + } + + public SubjectPublicKeyInfo( + DERConstructedSequence seq) + { + Enumeration e = seq.getObjects(); + + algId = new AlgorithmIdentifier((DERConstructedSequence)e.nextElement()); + + byte[] keyData = ((DERBitString)e.nextElement()).getBytes(); + + try + { + ByteArrayInputStream bIn = new ByteArrayInputStream(keyData); + DERInputStream dIn = new DERInputStream(bIn); + + pubKey = (DERObject)dIn.readObject(); + } + catch (IOException ex) + { + throw new IllegalArgumentException("error recovering public key"); + } + } + + public AlgorithmIdentifier getAlgorithmId() + { + return algId; + } + + public DERObject getPublicKey() + { + return pubKey; + } + + /** + *

+     * SubjectPublicKeyInfo ::= SEQUENCE {
+     *                          algorithm AlgorithmIdentifier,
+     *                          publicKey BIT STRING }
+     * 
+ */ + public DERObject getDERObject() + { + DERConstructedSequence seq = new DERConstructedSequence(); + + seq.addObject(algId); + seq.addObject(new DERBitString(pubKey)); + + return seq; + } +} diff --git a/src/org/bouncycastle/asn1/x509/TBSCertList.java b/src/org/bouncycastle/asn1/x509/TBSCertList.java new file mode 100644 index 0000000..11cb2ab --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/TBSCertList.java @@ -0,0 +1,192 @@ + +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.*; + +/** + * PKIX RFC-2459 + * + *
+ * TBSCertList  ::=  SEQUENCE  {
+ *      version                 Version OPTIONAL,
+ *                                   -- if present, shall be v2
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      thisUpdate              Time,
+ *      nextUpdate              Time OPTIONAL,
+ *      revokedCertificates     SEQUENCE OF SEQUENCE  {
+ *           userCertificate         CertificateSerialNumber,
+ *           revocationDate          Time,
+ *           crlEntryExtensions      Extensions OPTIONAL
+ *                                         -- if present, shall be v2
+ *                                }  OPTIONAL,
+ *      crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
+ *                                         -- if present, shall be v2
+ *                                }
+ * 
+ */ + +public class TBSCertList + implements DEREncodable +{ + public class CRLEntry + implements DEREncodable + { + DERConstructedSequence seq; + + DERInteger userCertificate; + DERUTCTime revocationDate; + X509Extensions crlEntryExtensions; + + public CRLEntry( + DERConstructedSequence seq) + { + this.seq = seq; + + userCertificate = (DERInteger)seq.getObjectAt(0); + revocationDate = (DERUTCTime)seq.getObjectAt(1); + if ( seq.getSize() == 3 ) + { + crlEntryExtensions = new X509Extensions((DERConstructedSequence)seq.getObjectAt(2)); + } + } + + public DERInteger getUserCertificate() + { + return userCertificate; + } + + public DERUTCTime getRevocationDate() + { + return revocationDate; + } + + public X509Extensions getExtensions() + { + return crlEntryExtensions; + } + + public DERObject getDERObject() + { + return seq; + } + } + + DERConstructedSequence seq; + + DERInteger version; + AlgorithmIdentifier signature; + X509Name issuer; + DERUTCTime thisUpdate; + DERUTCTime nextUpdate; + CRLEntry[] revokedCertificates; + X509Extensions crlExtensions; + + public TBSCertList( + DERConstructedSequence seq) + { + int seqPos = 0; + + this.seq = seq; + + if ( seq.getObjectAt(seqPos) instanceof DERInteger ) + { + version = (DERInteger)seq.getObjectAt(seqPos++); + } + else + { + version = new DERInteger(0); + } + + if ( seq.getObjectAt(seqPos) instanceof AlgorithmIdentifier ) + { + signature = (AlgorithmIdentifier)seq.getObjectAt(seqPos++); + } + else + { + signature = new AlgorithmIdentifier((DERConstructedSequence)seq.getObjectAt(seqPos++)); + } + + if ( seq.getObjectAt(seqPos) instanceof X509Name ) + { + issuer = (X509Name)seq.getObjectAt(seqPos++); + } + else + { + issuer = new X509Name((DERConstructedSequence)seq.getObjectAt(seqPos++)); + } + + thisUpdate = (DERUTCTime)seq.getObjectAt(seqPos++); + + if ( seqPos < seq.getSize() + && seq.getObjectAt(seqPos) instanceof DERUTCTime ) + { + nextUpdate = (DERUTCTime)seq.getObjectAt(seqPos++); + } + + if ( seqPos < seq.getSize() + && !(seq.getObjectAt(seqPos) instanceof DERTaggedObject) ) + { + DERConstructedSequence certs = (DERConstructedSequence)seq.getObjectAt(seqPos++); + revokedCertificates = new CRLEntry[certs.getSize()]; + + for ( int i = 0; i < revokedCertificates.length; i++ ) + { + revokedCertificates[i] = new CRLEntry((DERConstructedSequence)certs.getObjectAt(i)); + } + } + + if ( seqPos < seq.getSize() + && seq.getObjectAt(seqPos) instanceof DERTaggedObject ) + { + crlExtensions = new X509Extensions((DERConstructedSequence)((DERTaggedObject)seq.getObjectAt(seqPos++)).getObject()); + } + } + + public int getVersion() + { + return version.getValue().intValue() + 1; + } + + public DERInteger getVersionNumber() + { + return version; + } + + public AlgorithmIdentifier getSignature() + { + return signature; + } + + public X509Name getIssuer() + { + return issuer; + } + + public DERUTCTime getThisUpdate() + { + return thisUpdate; + } + + public DERUTCTime getNextUpdate() + { + return nextUpdate; + } + + public CRLEntry[] getRevokedCertificates() + { + return revokedCertificates; + } + + public X509Extensions getExtensions() + { + return crlExtensions; + } + + public DERObject getDERObject() + { + return seq; + } +} + diff --git a/src/org/bouncycastle/asn1/x509/TBSCertificateStructure.java b/src/org/bouncycastle/asn1/x509/TBSCertificateStructure.java new file mode 100644 index 0000000..d5c7cf7 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/TBSCertificateStructure.java @@ -0,0 +1,191 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.*; + +/** + *
+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      issuerUniqueID    [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      subjectUniqueID   [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      extensions        [ 3 ] Extensions OPTIONAL
+ *      }
+ * 
+ *

+ * Note: issuerUniqueID and subjectUniqueID are both deprecated by the IETF. This class + * will parse them, but you really shouldn't be creating new ones. + */ +public class TBSCertificateStructure + implements DEREncodable, X509ObjectIdentifiers, PKCSObjectIdentifiers +{ + DERConstructedSequence seq; + + DERInteger version; + DERInteger serialNumber; + AlgorithmIdentifier signature; + X509Name issuer; + DERUTCTime startDate, endDate; + X509Name subject; + SubjectPublicKeyInfo subjectPublicKeyInfo; + DERBitString issuerUniqueId; + DERBitString subjectUniqueId; + X509Extensions extensions; + + public TBSCertificateStructure( + DERConstructedSequence seq) + { + int seqStart = 0; + + this.seq = seq; + + // + // some certficates don't include a version number - we assume v1 + // + if (seq.getObjectAt(0) instanceof DERTaggedObject) + { + version = (DERInteger)((DERTaggedObject)seq.getObjectAt(0)).getObject(); + } + else + { + seqStart = -1; // field 0 is missing! + version = new DERInteger(0); + } + + serialNumber = (DERInteger)seq.getObjectAt(seqStart + 1); + + if (seq.getObjectAt(seqStart + 2) instanceof AlgorithmIdentifier) + { + signature = (AlgorithmIdentifier)seq.getObjectAt(seqStart + 2); + } + else + { + signature = new AlgorithmIdentifier((DERConstructedSequence)seq.getObjectAt(seqStart + 2)); + } + + if (seq.getObjectAt(seqStart + 3) instanceof X509Name) + { + issuer = (X509Name)seq.getObjectAt(seqStart + 3); + } + else + { + issuer = new X509Name((DERConstructedSequence)seq.getObjectAt(seqStart + 3)); + } + + // + // before and after dates + // + DERConstructedSequence dates = (DERConstructedSequence)seq.getObjectAt(seqStart + 4); + startDate = (DERUTCTime)dates.getObjectAt(0); + endDate = (DERUTCTime)dates.getObjectAt(1); + + if (seq.getObjectAt(seqStart + 5) instanceof X509Name) + { + subject = (X509Name)seq.getObjectAt(seqStart + 5); + } + else + { + subject = new X509Name((DERConstructedSequence)seq.getObjectAt(seqStart + 5)); + } + + // + // public key info. + // + if (seq.getObjectAt(seqStart + 6) instanceof SubjectPublicKeyInfo) + { + subjectPublicKeyInfo = (SubjectPublicKeyInfo)seq.getObjectAt(seqStart + 6); + } + else + { + subjectPublicKeyInfo = new SubjectPublicKeyInfo((DERConstructedSequence)seq.getObjectAt(seqStart + 6)); + } + + for (int extras = seq.getSize() - (seqStart + 6) - 1; extras > 0; extras--) + { + DERTaggedObject extra = (DERTaggedObject)seq.getObjectAt(seqStart + 6 + extras); + + switch (extra.getTagNo()) + { + case 1: + issuerUniqueId = (DERBitString)extra.getObject(); + break; + case 2: + subjectUniqueId = (DERBitString)extra.getObject(); + break; + case 3: + extensions = new X509Extensions((DERConstructedSequence)extra.getObject()); + } + } + } + + public int getVersion() + { + return version.getValue().intValue() + 1; + } + + public DERInteger getVersionNumber() + { + return version; + } + + public DERInteger getSerialNumber() + { + return serialNumber; + } + + public AlgorithmIdentifier getSignature() + { + return signature; + } + + public X509Name getIssuer() + { + return issuer; + } + + public DERUTCTime getStartDate() + { + return startDate; + } + + public DERUTCTime getEndDate() + { + return endDate; + } + + public X509Name getSubject() + { + return subject; + } + + public SubjectPublicKeyInfo getSubjectPublicKeyInfo() + { + return subjectPublicKeyInfo; + } + + public DERBitString getIssuerUniqueId() + { + return issuerUniqueId; + } + + public DERBitString getSubjectUniqueId() + { + return subjectUniqueId; + } + + public X509Extensions getExtensions() + { + return extensions; + } + + public DERObject getDERObject() + { + return seq; + } +} diff --git a/src/org/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java b/src/org/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java new file mode 100644 index 0000000..9cb5745 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/V1TBSCertificateGenerator.java @@ -0,0 +1,110 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.*; + +/** + * Generator for Version 1 TBSCertificateStructures. + *

+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      }
+ * 
+ * + */ +public class V1TBSCertificateGenerator +{ + DERTaggedObject version = new DERTaggedObject(0, new DERInteger(0)); + + DERInteger serialNumber; + AlgorithmIdentifier signature; + X509Name issuer; + DERUTCTime startDate, endDate; + X509Name subject; + SubjectPublicKeyInfo subjectPublicKeyInfo; + + public V1TBSCertificateGenerator() + { + } + + public void setSerialNumber( + DERInteger serialNumber) + { + this.serialNumber = serialNumber; + } + + public void setSignature( + AlgorithmIdentifier signature) + { + this.signature = signature; + } + + public void setIssuer( + X509Name issuer) + { + this.issuer = issuer; + } + + public void setStartDate( + DERUTCTime startDate) + { + this.startDate = startDate; + } + + public void setEndDate( + DERUTCTime endDate) + { + this.endDate = endDate; + } + + public void setSubject( + X509Name subject) + { + this.subject = subject; + } + + public void setSubjectPublicKeyInfo( + SubjectPublicKeyInfo pubKeyInfo) + { + this.subjectPublicKeyInfo = pubKeyInfo; + } + + public TBSCertificateStructure generateTBSCertificate() + { + if ((serialNumber == null) || (signature == null) + || (issuer == null) || (startDate == null) || (endDate == null) + || (subject == null) || (subjectPublicKeyInfo == null)) + { + throw new IllegalStateException("not all mandatory fields set in V1 TBScertificate generator"); + } + + DERConstructedSequence seq = new DERConstructedSequence(); + + seq.addObject(version); + seq.addObject(serialNumber); + seq.addObject(signature); + seq.addObject(issuer); + + // + // before and after dates + // + DERConstructedSequence validity = new DERConstructedSequence(); + + validity.addObject(startDate); + validity.addObject(endDate); + + seq.addObject(validity); + + seq.addObject(subject); + + seq.addObject(subjectPublicKeyInfo); + + return new TBSCertificateStructure(seq); + } +} diff --git a/src/org/bouncycastle/asn1/x509/V2TBSCertListGenerator.java b/src/org/bouncycastle/asn1/x509/V2TBSCertListGenerator.java new file mode 100644 index 0000000..92b82a3 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/V2TBSCertListGenerator.java @@ -0,0 +1,138 @@ +package org.bouncycastle.asn1.x509; + +import java.util.Vector; +import java.util.Enumeration; + +import org.bouncycastle.asn1.*; + +/** + * Generator for Version 2 TBSCertList structures. + *
+ *  TBSCertList  ::=  SEQUENCE  {
+ *       version                 Version OPTIONAL,
+ *                                    -- if present, shall be v2
+ *       signature               AlgorithmIdentifier,
+ *       issuer                  Name,
+ *       thisUpdate              Time,
+ *       nextUpdate              Time OPTIONAL,
+ *       revokedCertificates     SEQUENCE OF SEQUENCE  {
+ *            userCertificate         CertificateSerialNumber,
+ *            revocationDate          Time,
+ *            crlEntryExtensions      Extensions OPTIONAL
+ *                                          -- if present, shall be v2
+ *                                 }  OPTIONAL,
+ *       crlExtensions           [0]  EXPLICIT Extensions OPTIONAL
+ *                                          -- if present, shall be v2
+ *                                 }
+ * 
+ * + * Note: This class may be subject to change + */ +public class V2TBSCertListGenerator +{ + DERInteger version = new DERInteger(1); + + AlgorithmIdentifier signature; + X509Name issuer; + DERUTCTime thisUpdate, nextUpdate=null; + X509Extensions extensions=null; + private Vector crlentries=null; + + public V2TBSCertListGenerator() + { + } + + + public void setSignature( + AlgorithmIdentifier signature) + { + this.signature = signature; + } + + public void setIssuer( + X509Name issuer) + { + this.issuer = issuer; + } + + public void setThisUpdate( + DERUTCTime thisUpdate) + { + this.thisUpdate = thisUpdate; + } + + public void setNextUpdate( + DERUTCTime nextUpdate) + { + this.nextUpdate = nextUpdate; + } + + + public void addCRLEntry( + DERConstructedSequence crlEntry) + { + if (crlentries == null) + crlentries = new Vector(); + crlentries.addElement(crlEntry); + } + + public void addCRLEntry(DERInteger userCertificate, DERUTCTime revocationDate, int reason) + { + DERConstructedSequence seq = new DERConstructedSequence(); + seq.addObject(userCertificate); + seq.addObject(revocationDate); + if (reason != 0) + { + ReasonFlags rf = new ReasonFlags(reason); + DERConstructedSequence eseq = new DERConstructedSequence(); + eseq.addObject(X509Extensions.ReasonCode); + eseq.addObject(rf); + X509Extensions ex = new X509Extensions(eseq); + seq.addObject(ex); + } + if (crlentries == null) + crlentries = new Vector(); + crlentries.addElement(seq); + } + + public void setExtensions( + X509Extensions extensions) + { + this.extensions = extensions; + } + + public TBSCertList generateTBSCertList() + { + if ((signature == null) || (issuer == null) || (thisUpdate == null)) + { + throw new IllegalStateException("Not all mandatory fields set in V2 TBSCertList generator."); + } + + DERConstructedSequence seq = new DERConstructedSequence(); + + seq.addObject(version); + seq.addObject(signature); + seq.addObject(issuer); + + seq.addObject(thisUpdate); + if (nextUpdate != null) + seq.addObject(nextUpdate); + + // Add CRLEntries if they exist + if (crlentries != null) { + DERConstructedSequence certseq = new DERConstructedSequence(); + Enumeration it = crlentries.elements(); + while( it.hasMoreElements() ) { + certseq.addObject((DERConstructedSequence)it.nextElement()); + } + seq.addObject(certseq); + } + + if (extensions != null) + { + seq.addObject(new DERTaggedObject(0, extensions.getDERObject())); + } + + return new TBSCertList(seq); + } +} diff --git a/src/org/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java b/src/org/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java new file mode 100644 index 0000000..ae966ec --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/V3TBSCertificateGenerator.java @@ -0,0 +1,125 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.*; + +/** + * Generator for Version 3 TBSCertificateStructures. + *
+ * TBSCertificate ::= SEQUENCE {
+ *      version          [ 0 ]  Version DEFAULT v1(0),
+ *      serialNumber            CertificateSerialNumber,
+ *      signature               AlgorithmIdentifier,
+ *      issuer                  Name,
+ *      validity                Validity,
+ *      subject                 Name,
+ *      subjectPublicKeyInfo    SubjectPublicKeyInfo,
+ *      issuerUniqueID    [ 1 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      subjectUniqueID   [ 2 ] IMPLICIT UniqueIdentifier OPTIONAL,
+ *      extensions        [ 3 ] Extensions OPTIONAL
+ *      }
+ * 
+ * + */ +public class V3TBSCertificateGenerator +{ + DERTaggedObject version = new DERTaggedObject(0, new DERInteger(2)); + + DERInteger serialNumber; + AlgorithmIdentifier signature; + X509Name issuer; + DERUTCTime startDate, endDate; + X509Name subject; + SubjectPublicKeyInfo subjectPublicKeyInfo; + X509Extensions extensions; + + public V3TBSCertificateGenerator() + { + } + + public void setSerialNumber( + DERInteger serialNumber) + { + this.serialNumber = serialNumber; + } + + public void setSignature( + AlgorithmIdentifier signature) + { + this.signature = signature; + } + + public void setIssuer( + X509Name issuer) + { + this.issuer = issuer; + } + + public void setStartDate( + DERUTCTime startDate) + { + this.startDate = startDate; + } + + public void setEndDate( + DERUTCTime endDate) + { + this.endDate = endDate; + } + + public void setSubject( + X509Name subject) + { + this.subject = subject; + } + + public void setSubjectPublicKeyInfo( + SubjectPublicKeyInfo pubKeyInfo) + { + this.subjectPublicKeyInfo = pubKeyInfo; + } + + public void setExtensions( + X509Extensions extensions) + { + this.extensions = extensions; + } + + public TBSCertificateStructure generateTBSCertificate() + { + if ((serialNumber == null) || (signature == null) + || (issuer == null) || (startDate == null) || (endDate == null) + || (subject == null) || (subjectPublicKeyInfo == null)) + { + throw new IllegalStateException("not all mandatory fields set in V3 TBScertificate generator"); + } + + DERConstructedSequence seq = new DERConstructedSequence(); + + seq.addObject(version); + seq.addObject(serialNumber); + seq.addObject(signature); + seq.addObject(issuer); + + // + // before and after dates + // + DERConstructedSequence validity = new DERConstructedSequence(); + + validity.addObject(startDate); + validity.addObject(endDate); + + seq.addObject(validity); + + seq.addObject(subject); + + seq.addObject(subjectPublicKeyInfo); + + if (extensions != null) + { + seq.addObject(new DERTaggedObject(3, extensions.getDERObject())); + } + + return new TBSCertificateStructure(seq); + } +} diff --git a/src/org/bouncycastle/asn1/x509/X509CertificateStructure.java b/src/org/bouncycastle/asn1/x509/X509CertificateStructure.java new file mode 100644 index 0000000..68479c5 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/X509CertificateStructure.java @@ -0,0 +1,110 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; +import org.bouncycastle.asn1.pkcs.*; + +/** + * an X509Certificate structure. + *
+ *  Certificate ::= SEQUENCE {
+ *      tbsCertificate          TBSCertificate,
+ *      signatureAlgorithm      AlgorithmIdentifier,
+ *      signature               BIT STRING
+ *  }
+ * 
+ */ +public class X509CertificateStructure + implements DEREncodable, X509ObjectIdentifiers, PKCSObjectIdentifiers +{ + DERConstructedSequence seq; + TBSCertificateStructure tbsCert; + AlgorithmIdentifier sigAlgId; + DERBitString sig; + + public X509CertificateStructure( + DERConstructedSequence seq) + { + this.seq = seq; + + // + // correct x509 certficate + // + if (seq.getSize() == 3) + { + if (seq.getObjectAt(0) instanceof TBSCertificateStructure) + { + tbsCert = (TBSCertificateStructure)seq.getObjectAt(0); + } + else + { + tbsCert = new TBSCertificateStructure((DERConstructedSequence)seq.getObjectAt(0)); + } + + if (seq.getObjectAt(1) instanceof AlgorithmIdentifier) + { + sigAlgId = (AlgorithmIdentifier)seq.getObjectAt(1); + } + else + { + sigAlgId = new AlgorithmIdentifier((DERConstructedSequence)seq.getObjectAt(1)); + } + + sig = (DERBitString)seq.getObjectAt(2); + } + } + + public TBSCertificateStructure getTBSCertificate() + { + return tbsCert; + } + + public int getVersion() + { + return tbsCert.getVersion(); + } + + public DERInteger getSerialNumber() + { + return tbsCert.getSerialNumber(); + } + + public X509Name getIssuer() + { + return tbsCert.getIssuer(); + } + + public DERUTCTime getStartDate() + { + return tbsCert.getStartDate(); + } + + public DERUTCTime getEndDate() + { + return tbsCert.getEndDate(); + } + + public X509Name getSubject() + { + return tbsCert.getSubject(); + } + + public SubjectPublicKeyInfo getSubjectPublicKeyInfo() + { + return tbsCert.getSubjectPublicKeyInfo(); + } + + public AlgorithmIdentifier getSignatureAlgorithm() + { + return sigAlgId; + } + + public DERBitString getSignature() + { + return sig; + } + + public DERObject getDERObject() + { + return seq; + } +} diff --git a/src/org/bouncycastle/asn1/x509/X509Extension.java b/src/org/bouncycastle/asn1/x509/X509Extension.java new file mode 100644 index 0000000..f2fb1b9 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/X509Extension.java @@ -0,0 +1,63 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.*; + +/** + * an object for the elements in the X.509 V3 extension block. + */ +public class X509Extension +{ + boolean critical; + DEROctetString value; + + public X509Extension( + DERBoolean critical, + DEROctetString value) + { + this.critical = critical.isTrue(); + this.value = value; + } + + public X509Extension( + boolean critical, + DEROctetString value) + { + this.critical = critical; + this.value = value; + } + + public boolean isCritical() + { + return critical; + } + + public DEROctetString getValue() + { + return value; + } + + public int hashCode() + { + if (this.isCritical()) + { + return this.getValue().hashCode(); + } + + + return ~this.getValue().hashCode(); + } + + public boolean equals( + Object o) + { + if (o == null || !(o instanceof X509Extension)) + { + return false; + } + + X509Extension other = (X509Extension)o; + + return other.getValue().equals(this.getValue()) + && (other.isCritical() == this.isCritical()); + } +} diff --git a/src/org/bouncycastle/asn1/x509/X509Extensions.java b/src/org/bouncycastle/asn1/x509/X509Extensions.java new file mode 100644 index 0000000..7fb75f6 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/X509Extensions.java @@ -0,0 +1,267 @@ +package org.bouncycastle.asn1.x509; + +import java.io.*; +import java.util.*; + +import org.bouncycastle.asn1.*; + +public class X509Extensions + implements DEREncodable +{ + /** + * Subject Key Identifier + */ + public static final DERObjectIdentifier SubjectKeyIdentifier = new DERObjectIdentifier("2.5.29.14"); + + /** + * Key Usage + */ + public static final DERObjectIdentifier KeyUsage = new DERObjectIdentifier("2.5.29.15"); + + /** + * Private Key Usage Period + */ + public static final DERObjectIdentifier PrivateKeyUsagePeriod = new DERObjectIdentifier("2.5.29.16"); + + /** + * Subject Alternative Name + */ + public static final DERObjectIdentifier SubjectAlternativeName = new DERObjectIdentifier("2.5.29.17"); + + /** + * Issuer Alternative Name + */ + public static final DERObjectIdentifier IssuerAlternativeName = new DERObjectIdentifier("2.5.29.18"); + + /** + * Basic Constraints + */ + public static final DERObjectIdentifier BasicConstraints = new DERObjectIdentifier("2.5.29.19"); + + /** + * CRL Number + */ + public static final DERObjectIdentifier CRLNumber = new DERObjectIdentifier("2.5.29.20"); + + /** + * Reason code + */ + public static final DERObjectIdentifier ReasonCode = new DERObjectIdentifier("2.5.29.21"); + + /** + * Hold Instruction Code + */ + public static final DERObjectIdentifier InstructionCode = new DERObjectIdentifier("2.5.29.23"); + + /** + * Invalidity Date + */ + public static final DERObjectIdentifier InvalidityDate = new DERObjectIdentifier("2.5.29.24"); + + /** + * Delta CRL indicator + */ + public static final DERObjectIdentifier DeltaCRLIndicator = new DERObjectIdentifier("2.5.29.27"); + + /** + * Issuing Distribution Point + */ + public static final DERObjectIdentifier IssuingDistributionPoint = new DERObjectIdentifier("2.5.29.28"); + + /** + * Certificate Issuer + */ + public static final DERObjectIdentifier CertificateIssuer = new DERObjectIdentifier("2.5.29.29"); + + /** + * Name Constraints + */ + public static final DERObjectIdentifier NameConstraints = new DERObjectIdentifier("2.5.29.30"); + + /** + * CRL Distribution Points + */ + public static final DERObjectIdentifier CRLDistributionPoints = new DERObjectIdentifier("2.5.29.31"); + + /** + * Certificate Policies + */ + public static final DERObjectIdentifier CertificatePolicies = new DERObjectIdentifier("2.5.29.32"); + + /** + * Policy Mappings + */ + public static final DERObjectIdentifier PolicyMappings = new DERObjectIdentifier("2.5.29.33"); + + /** + * Authority Key Identifier + */ + public static final DERObjectIdentifier AuthorityKeyIdentifier = new DERObjectIdentifier("2.5.29.35"); + + /** + * Policy Constraints + */ + public static final DERObjectIdentifier PolicyConstraints = new DERObjectIdentifier("2.5.29.36"); + + private Hashtable extensions = new Hashtable(); + private Vector ordering = new Vector(); + private DERConstructedSequence seq; + + /** + * Constructor from DERConstructedSequence. + * + * the extensions are a list of constructed sequences, either with (OID, OctetString) or (OID, Boolean, OctetString) + */ + public X509Extensions( + DERConstructedSequence seq) + { + this.seq = seq; + + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + DERConstructedSequence s = (DERConstructedSequence)e.nextElement(); + Enumeration e1 = s.getObjects(); + + if (s.getSize() == 3) + { + extensions.put(s.getObjectAt(0), new X509Extension((DERBoolean)s.getObjectAt(1), (DEROctetString)s.getObjectAt(2))); + } + else + { + extensions.put(s.getObjectAt(0), new X509Extension(false, (DEROctetString)s.getObjectAt(1))); + } + + ordering.addElement(s.getObjectAt(0)); + } + } + + /** + * constructor from a table of extensions. + *

+ * it's is assumed the table contains OID/String pairs. + */ + public X509Extensions( + Hashtable extensions) + { + this(null, extensions); + } + + /** + * constructor from a table of extensions with ordering + *

+ * it's is assumed the table contains OID/String pairs. + */ + public X509Extensions( + Vector ordering, + Hashtable extensions) + { + this.seq = new DERConstructedSequence(); + + if (ordering == null) + { + Enumeration e = extensions.keys(); + + ordering = this.ordering; + + while (e.hasMoreElements()) + { + this.ordering.addElement(e.nextElement()); + } + } + + Enumeration e = ordering.elements(); + + while (e.hasMoreElements()) + { + DERObjectIdentifier oid = (DERObjectIdentifier)e.nextElement(); + X509Extension ext = (X509Extension)extensions.get(oid); + DERConstructedSequence s = new DERConstructedSequence(); + + s.addObject(oid); + + if (ext.isCritical()) + { + s.addObject(new DERBoolean(true)); + } + + s.addObject(ext.getValue()); + + seq.addObject(s); + } + } + + /** + * return an Enumeration of the extension field's object ids. + */ + public Enumeration oids() + { + return ordering.elements(); + } + + /** + * return the extension represented by the object identifier + * passed in. + * + * @return the extension if it's present, null otherwise. + */ + public X509Extension getExtension( + DERObjectIdentifier oid) + { + return (X509Extension)extensions.get(oid); + } + + public DERObject getDERObject() + { + return seq; + } + + public int hashCode() + { + Enumeration e = extensions.keys(); + int hashCode = 0; + + while (e.hasMoreElements()) + { + Object o = e.nextElement(); + + hashCode ^= o.hashCode(); + hashCode ^= extensions.get(o).hashCode(); + } + + return hashCode; + } + + public boolean equals( + Object o) + { + if (o == null || !(o instanceof X509Extensions)) + { + return false; + } + + X509Extensions other = (X509Extensions)o; + + Enumeration e1 = extensions.keys(); + Enumeration e2 = other.extensions.keys(); + + while (e1.hasMoreElements() && e2.hasMoreElements()) + { + Object o1 = e1.nextElement(); + Object o2 = e2.nextElement(); + + if (!o1.equals(o2)) + { + return false; + } + } + + if (e1.hasMoreElements() || e2.hasMoreElements()) + { + return false; + } + + return true; + } +} diff --git a/src/org/bouncycastle/asn1/x509/X509Name.java b/src/org/bouncycastle/asn1/x509/X509Name.java new file mode 100644 index 0000000..2f2a60f --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/X509Name.java @@ -0,0 +1,336 @@ +package org.bouncycastle.asn1.x509; + +import java.io.*; +import java.util.*; + +import org.bouncycastle.asn1.*; + +public class X509Name + implements DEREncodable +{ + /** + * country code - StringType(SIZE(2)) + */ + public static final DERObjectIdentifier C = new DERObjectIdentifier("2.5.4.6"); + + /** + * organization - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier O = new DERObjectIdentifier("2.5.4.10"); + + /** + * organizational unit name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier OU = new DERObjectIdentifier("2.5.4.11"); + + /** + * common name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier CN = new DERObjectIdentifier("2.5.4.3"); + + /** + * device serial number name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier SN = new DERObjectIdentifier("2.5.4.5"); + + /** + * locality name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier L = new DERObjectIdentifier("2.5.4.7"); + + /** + * state, or province name - StringType(SIZE(1..64)) + */ + public static final DERObjectIdentifier ST = new DERObjectIdentifier("2.5.4.8"); + + /** + * email address (RSA PKCS#9 extension) - IA5String + *

+ * note: if you're trying to be ultra orthodox, don't use this! It shouldn't be in here. + */ + public static final DERObjectIdentifier EmailAddress = new DERObjectIdentifier("1.2.840.113549.1.9.1"); + + /** + * look up table translating OID values into their common symbols. + */ + public static Hashtable OIDLookUp = new Hashtable(); + + /** + * look up table translating common symbols into their OIDS. + */ + public static Hashtable SymbolLookUp = new Hashtable(); + + static + { + OIDLookUp.put(C, "C"); + OIDLookUp.put(O, "O"); + OIDLookUp.put(OU, "OU"); + OIDLookUp.put(CN, "CN"); + OIDLookUp.put(L, "L"); + OIDLookUp.put(ST, "ST"); + OIDLookUp.put(SN, "SN"); + OIDLookUp.put(EmailAddress, "EmailAddress"); + + SymbolLookUp.put("C", C); + SymbolLookUp.put("O", O); + SymbolLookUp.put("OU", OU); + SymbolLookUp.put("CN", CN); + SymbolLookUp.put("L", L); + SymbolLookUp.put("ST", ST); + SymbolLookUp.put("SN", SN); + SymbolLookUp.put("EmailAddress", EmailAddress); + } + + private Vector ordering = new Vector(); + private Hashtable attributes = new Hashtable(); + private DERConstructedSequence seq = null; + + /** + * Constructor from DERConstructedSequence. + * + * the principal will be a list of constructed sets, each containing an (OID, String) pair. + */ + public X509Name( + DERConstructedSequence seq) + { + this.seq = seq; + + Enumeration e = seq.getObjects(); + + while (e.hasMoreElements()) + { + DERSet set = (DERSet)e.nextElement(); + DERConstructedSequence s = (DERConstructedSequence)set.getSequence(); + + ordering.addElement(s.getObjectAt(0)); + attributes.put(s.getObjectAt(0), ((DERString)s.getObjectAt(1)).getString()); + } + } + + /** + * constructor from a table of attributes. + *

+ * it's is assumed the table contains OID/String pairs, and the contents + * of the table are copied into an internal table as part of the + * construction process. + *

+ * Note: if the name you are trying to generate should be + * following a specific ordering, you should use the constructor + * with the ordering specified below. + */ + public X509Name( + Hashtable attributes) + { + this(null, attributes); + } + + /** + * constructor from a table of attributes with ordering. + *

+ * it's is assumed the table contains OID/String pairs, and the contents + * of the table are copied into an internal table as part of the + * construction process. The ordering vector should contain the OIDs + * in the order they are meant to be encoded or printed in toString. + */ + public X509Name( + Vector ordering, + Hashtable attributes) + { + if (ordering != null) + { + for (int i = 0; i != ordering.size(); i++) + { + this.ordering.addElement(ordering.elementAt(i)); + } + } + else + { + Enumeration e = attributes.keys(); + + while (e.hasMoreElements()) + { + this.ordering.addElement(e.nextElement()); + } + } + + for (int i = 0; i != this.ordering.size(); i++) + { + DERObjectIdentifier oid = (DERObjectIdentifier)this.ordering.elementAt(i); + + if (OIDLookUp.get(oid) == null) + { + throw new IllegalArgumentException("Unknown object id - " + oid.getId() + " - passed to distinguished name"); + } + + if (attributes.get(oid) == null) + { + throw new IllegalArgumentException("No attribute for object id - " + oid.getId() + " - passed to distinguished name"); + } + + this.attributes.put(oid, attributes.get(oid)); // copy the hash table + } + } + + /** + * takes an X509 dir name as a string of the format "C=AU, ST=Victoria", or + * some such, converting it into an ordered set of name attributes. + */ + public X509Name( + String dirName) + { + X509NameTokenizer nTok = new X509NameTokenizer(dirName); + + while (nTok.hasMoreTokens()) + { + String token = nTok.nextToken(); + int index = token.indexOf('='); + + if (index == -1) + { + throw new IllegalArgumentException("badly formated directory string"); + } + + String name = token.substring(0, index); + String value = token.substring(index + 1); + + DERObjectIdentifier oid = (DERObjectIdentifier)SymbolLookUp.get(name); + if (oid == null) + { + throw new IllegalArgumentException("Unknown object id - " + oid.getId() + " - passed to distinguished name"); + } + + this.ordering.addElement(oid); + this.attributes.put(oid, value); + } + } + + public DERObject getDERObject() + { + if (seq == null) + { + seq = new DERConstructedSequence(); + + for (int i = 0; i != ordering.size(); i++) + { + DERConstructedSequence s = new DERConstructedSequence(); + DERObjectIdentifier oid = (DERObjectIdentifier)ordering.elementAt(i); + + s.addObject(oid); + if (oid.equals(EmailAddress)) + { + s.addObject(new DERIA5String((String)attributes.get(oid))); + } + else + { + s.addObject(new DERPrintableString((String)attributes.get(oid))); + } + + seq.addObject(new DERSet(s)); + } + } + + return seq; + } + + public int hashCode() + { + Enumeration e = attributes.keys(); + int hashCode = 0; + + while (e.hasMoreElements()) + { + Object o = e.nextElement(); + + hashCode ^= o.hashCode(); + hashCode ^= attributes.get(o).hashCode(); + } + + for (int i = 0; i != ordering.size(); i++) + { + hashCode ^= ordering.elementAt(i).hashCode(); + } + + return hashCode; + } + + public boolean equals( + Object o) + { + if (o == null || !(o instanceof X509Name)) + { + return false; + } + + X509Name other = (X509Name)o; + + if (ordering.size() != other.ordering.size()) + { + return false; + } + + for (int i = 0; i != ordering.size(); i++) + { + if (!ordering.elementAt(i).equals(other.ordering.elementAt(i))) + { + return false; + } + } + + Enumeration e1 = attributes.keys(); + Enumeration e2 = other.attributes.keys(); + + while (e1.hasMoreElements() && e2.hasMoreElements()) + { + Object o1 = e1.nextElement(); + Object o2 = e2.nextElement(); + + if (!o1.equals(o2)) + { + return false; + } + } + + if (e1.hasMoreElements() || e2.hasMoreElements()) + { + return false; + } + + return true; + } + + public String toString() + { + StringBuffer buf = new StringBuffer(); + boolean first = true; + + for (int i = 0; i != ordering.size(); i++) + { + Object oid = ordering.elementAt(i); + String sym = (String)OIDLookUp.get(oid); + + if (first) + { + first = false; + } + else + { + buf.append(", "); + } + + if (sym != null) + { + buf.append(sym); + buf.append("="); + buf.append((String)attributes.get(oid)); + } + else + { + buf.append(((DERObjectIdentifier)oid).getId()); + buf.append("="); + buf.append((String)attributes.get(oid)); + } + } + + return buf.toString(); + } +} diff --git a/src/org/bouncycastle/asn1/x509/X509NameTokenizer.java b/src/org/bouncycastle/asn1/x509/X509NameTokenizer.java new file mode 100644 index 0000000..2474027 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/X509NameTokenizer.java @@ -0,0 +1,48 @@ +package org.bouncycastle.asn1.x509; + +/** + * class for breaking up an X509 Name into it's component tokens, ala + * java.util.StringTokenizer. We need this class as some of the + * lightweight Java environment don't support classes like + * StringTokenizer. + */ +public class X509NameTokenizer +{ + private String oid; + private int index; + + public X509NameTokenizer( + String oid) + { + this.oid = oid; + this.index = 0; + } + + public boolean hasMoreTokens() + { + return (index != -1); + } + + public String nextToken() + { + if (index == -1) + { + return null; + } + + String token; + int end = oid.indexOf(',', index); + + if (end == -1) + { + token = oid.substring(index); + index = -1; + return token.trim(); + } + + token = oid.substring(index, end); + + index = end + 1; + return token.trim(); + } +} diff --git a/src/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java b/src/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java new file mode 100644 index 0000000..18ce5c1 --- /dev/null +++ b/src/org/bouncycastle/asn1/x509/X509ObjectIdentifiers.java @@ -0,0 +1,38 @@ +package org.bouncycastle.asn1.x509; + +import org.bouncycastle.asn1.DERObjectIdentifier; + +public interface X509ObjectIdentifiers +{ + // + // base id + // + static final String id = "2.5.4"; + + static final DERObjectIdentifier commonName = new DERObjectIdentifier(id + ".3"); + static final DERObjectIdentifier countryName = new DERObjectIdentifier(id + ".6"); + static final DERObjectIdentifier localityName = new DERObjectIdentifier(id + ".7"); + static final DERObjectIdentifier stateOrProvinceName = new DERObjectIdentifier(id + ".8"); + static final DERObjectIdentifier organization = new DERObjectIdentifier(id + ".10"); + static final DERObjectIdentifier organizationalUnitName = new DERObjectIdentifier(id + ".11"); + + // id-SHA1 OBJECT IDENTIFIER ::= + // {iso(1) identified-organization(3) oiw(14) secsig(3) algorithms(2) 26 } // + static final DERObjectIdentifier id_SHA1 = new DERObjectIdentifier("1.3.14.3.2.26"); + + // + // ripemd160 OBJECT IDENTIFIER ::= + // {iso(1) identified-organization(3) TeleTrust(36) algorithm(3) hashAlgorithm(2) RIPEMD-160(1)} + // + static final DERObjectIdentifier ripemd160 = new DERObjectIdentifier("1.3.36.3.2.1"); + + // + // ripemd160WithRSAEncryption OBJECT IDENTIFIER ::= + // {iso(1) identified-organization(3) TeleTrust(36) algorithm(3) signatureAlgorithm(3) rsaSignature(1) rsaSignatureWithripemd160(2) } + // + static final DERObjectIdentifier ripemd160WithRSAEncryption = new DERObjectIdentifier("1.3.36.3.3.1.2"); + + + static final DERObjectIdentifier id_ea_rsa = new DERObjectIdentifier("2.5.8.1.1"); +} + diff --git a/src/org/bouncycastle/crypto/AsymmetricBlockCipher.java b/src/org/bouncycastle/crypto/AsymmetricBlockCipher.java new file mode 100644 index 0000000..23293f9 --- /dev/null +++ b/src/org/bouncycastle/crypto/AsymmetricBlockCipher.java @@ -0,0 +1,46 @@ +package org.bouncycastle.crypto; + +import java.math.BigInteger; + +/** + * base interface that a public/private key block cipher needs + * to conform to. + */ +public interface AsymmetricBlockCipher +{ + /** + * initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + */ + public void init(boolean forEncryption, CipherParameters param); + + /** + * returns the largest size an input block can be. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize(); + + /** + * returns the maximum size of the block produced by this cipher. + * + * @return maximum size of the output block produced by the cipher. + */ + public int getOutputBlockSize(); + + /** + * process the block of len bytes stored in in from offset inOff. + * + * @param in the input data + * @param inOff offset into the in array where the data starts + * @param len the length of the block to be processed. + * @return the resulting byte array of the encryption/decryption process. + * @exception InvalidCipherTextException data decrypts improperly. + * @exception DataLengthException the input data is too large for the cipher. + */ + public byte[] processBlock(byte[] in, int inOff, int len) + throws InvalidCipherTextException; +} diff --git a/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java b/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java new file mode 100644 index 0000000..85bec73 --- /dev/null +++ b/src/org/bouncycastle/crypto/AsymmetricCipherKeyPair.java @@ -0,0 +1,44 @@ +package org.bouncycastle.crypto; + +/** + * a holding class for public/private parameter pairs. + */ +public class AsymmetricCipherKeyPair +{ + private CipherParameters publicParam; + private CipherParameters privateParam; + + /** + * basic constructor. + * + * @param publicParam a public key parameters object. + * @param privateParam the corresponding private key parameters. + */ + public AsymmetricCipherKeyPair( + CipherParameters publicParam, + CipherParameters privateParam) + { + this.publicParam = publicParam; + this.privateParam = privateParam; + } + + /** + * return the public key parameters. + * + * @return the public key parameters. + */ + public CipherParameters getPublic() + { + return publicParam; + } + + /** + * return the private key parameters. + * + * @return the private key parameters. + */ + public CipherParameters getPrivate() + { + return privateParam; + } +} diff --git a/src/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java b/src/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java new file mode 100644 index 0000000..236ebbe --- /dev/null +++ b/src/org/bouncycastle/crypto/AsymmetricCipherKeyPairGenerator.java @@ -0,0 +1,22 @@ +package org.bouncycastle.crypto; + +/** + * interface that a public/private key pair generator should conform to. + */ +public interface AsymmetricCipherKeyPairGenerator +{ + /** + * intialise the key pair generator. + * + * @param the parameters the key pair is to be initialised with. + */ + public void init(KeyGenerationParameters param); + + /** + * return an AsymmetricCipherKeyPair containing the generated keys. + * + * @return an AsymmetricCipherKeyPair containing the generated keys. + */ + public AsymmetricCipherKeyPair generateKeyPair(); +} + diff --git a/src/org/bouncycastle/crypto/BlockCipher.java b/src/org/bouncycastle/crypto/BlockCipher.java new file mode 100644 index 0000000..0b99ee7 --- /dev/null +++ b/src/org/bouncycastle/crypto/BlockCipher.java @@ -0,0 +1,57 @@ +package org.bouncycastle.crypto; + +import java.lang.IllegalStateException; + +/** + * Block cipher engines are expected to conform to this interface. + */ +public interface BlockCipher +{ + /** + * Initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public String getAlgorithmName(); + + /** + * Return the block size for this cipher (in bytes). + * + * @return the block size for this cipher in bytes. + */ + public int getBlockSize(); + + /** + * Process one block of input from the array in and write it to + * the out array. + * + * @param in the array containing the input data. + * @param inOff offset into the in array the data starts at. + * @param out the array the output data will be copied into. + * @param outOff the offset into the out array the output will start at. + * @exception DataLengthException if there isn't enough data in in, or + * space in out. + * @exception IllegalStateException if the cipher isn't initialised. + * @return the number of bytes processed and produced. + */ + public int processBlock(byte[] in, int inOff, byte[] out, int outOff) + throws DataLengthException, IllegalStateException; + + /** + * Reset the cipher. After resetting the cipher is in the same state + * as it was after the last init (if there was one). + */ + public void reset(); +} diff --git a/src/org/bouncycastle/crypto/CipherKeyGenerator.java b/src/org/bouncycastle/crypto/CipherKeyGenerator.java new file mode 100644 index 0000000..451f8e8 --- /dev/null +++ b/src/org/bouncycastle/crypto/CipherKeyGenerator.java @@ -0,0 +1,38 @@ +package org.bouncycastle.crypto; + +import java.security.SecureRandom; + +/** + * The base class for symmetric, or secret, cipher key generators. + */ +public class CipherKeyGenerator +{ + protected SecureRandom random; + protected int strength; + + /** + * initialise the key generator. + * + * @param param the parameters to be used for key generation + */ + public void init( + KeyGenerationParameters param) + { + this.random = param.getRandom(); + this.strength = (param.getStrength() + 7) / 8; + } + + /** + * generate a secret key. + * + * @return a byte array containing the key value. + */ + public byte[] generateKey() + { + byte[] key = new byte[strength]; + + random.nextBytes(key); + + return key; + } +} diff --git a/src/org/bouncycastle/crypto/CipherParameters.java b/src/org/bouncycastle/crypto/CipherParameters.java new file mode 100644 index 0000000..5be8730 --- /dev/null +++ b/src/org/bouncycastle/crypto/CipherParameters.java @@ -0,0 +1,8 @@ +package org.bouncycastle.crypto; + +/** + * all parameter classes implement this. + */ +public interface CipherParameters +{ +} diff --git a/src/org/bouncycastle/crypto/CryptoException.java b/src/org/bouncycastle/crypto/CryptoException.java new file mode 100644 index 0000000..dc4a8df --- /dev/null +++ b/src/org/bouncycastle/crypto/CryptoException.java @@ -0,0 +1,26 @@ +package org.bouncycastle.crypto; + +/** + * the foundation class for the hard exceptions thrown by the crypto packages. + */ +public class CryptoException + extends Exception +{ + /** + * base constructor. + */ + public CryptoException() + { + } + + /** + * create a CryptoException with the given message. + * + * @param message the message to be carried with the exception. + */ + public CryptoException( + String message) + { + super(message); + } +} diff --git a/src/org/bouncycastle/crypto/DataLengthException.java b/src/org/bouncycastle/crypto/DataLengthException.java new file mode 100644 index 0000000..fbf047c --- /dev/null +++ b/src/org/bouncycastle/crypto/DataLengthException.java @@ -0,0 +1,29 @@ +package org.bouncycastle.crypto; + +/** + * this exception is thrown if a buffer that is meant to have output + * copied into it turns out to be too short, or if we've been given + * insufficient input. In general this exception will get thrown rather + * than an ArrayOutOfBounds exception. + */ +public class DataLengthException + extends RuntimeCryptoException +{ + /** + * base constructor. + */ + public DataLengthException() + { + } + + /** + * create a DataLengthException with the given message. + * + * @param message the message to be carried with the exception. + */ + public DataLengthException( + String message) + { + super(message); + } +} diff --git a/src/org/bouncycastle/crypto/Digest.java b/src/org/bouncycastle/crypto/Digest.java new file mode 100644 index 0000000..844c28b --- /dev/null +++ b/src/org/bouncycastle/crypto/Digest.java @@ -0,0 +1,51 @@ +package org.bouncycastle.crypto; + +/** + * interface that a message digest conforms to. + */ +public interface Digest +{ + /** + * return the algorithm name + * + * @return the algorithm name + */ + public String getAlgorithmName(); + + /** + * return the size, in bytes, of the digest produced by this message digest. + * + * @return the size, in bytes, of the digest produced by this message digest. + */ + public int getDigestSize(); + + /** + * update the message digest with a single byte. + * + * @param in the input byte to be entered. + */ + public void update(byte in); + + /** + * update the message digest with a block of bytes. + * + * @param in the byte array containing the data. + * @param inOff the offset into the byte array where the data starts. + * @param len the length of the data. + */ + public void update(byte[] in, int inOff, int len); + + /** + * close the digest, producing the final digest value. The doFinal + * call leaves the digest reset. + * + * @param out the array the digest is to be copied into. + * @param outOff the offset into the out array the digest is to start at. + */ + public int doFinal(byte[] out, int outOff); + + /** + * reset the digest back to it's initial state. + */ + public void reset(); +} diff --git a/src/org/bouncycastle/crypto/InvalidCipherTextException.java b/src/org/bouncycastle/crypto/InvalidCipherTextException.java new file mode 100644 index 0000000..59e4b26 --- /dev/null +++ b/src/org/bouncycastle/crypto/InvalidCipherTextException.java @@ -0,0 +1,27 @@ +package org.bouncycastle.crypto; + +/** + * this exception is thrown whenever we find something we don't expect in a + * message. + */ +public class InvalidCipherTextException + extends CryptoException +{ + /** + * base constructor. + */ + public InvalidCipherTextException() + { + } + + /** + * create a InvalidCipherTextException with the given message. + * + * @param message the message to be carried with the exception. + */ + public InvalidCipherTextException( + String message) + { + super(message); + } +} diff --git a/src/org/bouncycastle/crypto/KeyGenerationParameters.java b/src/org/bouncycastle/crypto/KeyGenerationParameters.java new file mode 100644 index 0000000..9a63522 --- /dev/null +++ b/src/org/bouncycastle/crypto/KeyGenerationParameters.java @@ -0,0 +1,48 @@ +package org.bouncycastle.crypto; + +import java.security.SecureRandom; + +/** + * The base class for parameters to key generators. + */ +public class KeyGenerationParameters +{ + private SecureRandom random; + private int strength; + + /** + * initialise the generator with a source of randomness + * and a strength (in bits). + * + * @param random the random byte source. + * @param strength the size, in bits, of the keys we want to produce. + */ + public KeyGenerationParameters( + SecureRandom random, + int strength) + { + this.random = random; + this.strength = strength; + } + + /** + * return the random source associated with this + * generator. + * + * @return the generators random source. + */ + public SecureRandom getRandom() + { + return random; + } + + /** + * return the bit strength for keys produced by this generator, + * + * @return the strength of the keys this generator produces (in bits). + */ + public int getStrength() + { + return strength; + } +} diff --git a/src/org/bouncycastle/crypto/RuntimeCryptoException.java b/src/org/bouncycastle/crypto/RuntimeCryptoException.java new file mode 100644 index 0000000..c157202 --- /dev/null +++ b/src/org/bouncycastle/crypto/RuntimeCryptoException.java @@ -0,0 +1,26 @@ +package org.bouncycastle.crypto; + +/** + * the foundation class for the exceptions thrown by the crypto packages. + */ +public class RuntimeCryptoException + extends RuntimeException +{ + /** + * base constructor. + */ + public RuntimeCryptoException() + { + } + + /** + * create a RuntimeCryptoException with the given message. + * + * @param message the message to be carried with the exception. + */ + public RuntimeCryptoException( + String message) + { + super(message); + } +} diff --git a/src/org/bouncycastle/crypto/StreamBlockCipher.java b/src/org/bouncycastle/crypto/StreamBlockCipher.java new file mode 100644 index 0000000..bea3b6a --- /dev/null +++ b/src/org/bouncycastle/crypto/StreamBlockCipher.java @@ -0,0 +1,108 @@ +package org.bouncycastle.crypto; + +/** + * a wrapper for block ciphers with a single byte block size, so that they + * can be treated like stream ciphers. + */ +public class StreamBlockCipher + implements StreamCipher +{ + private BlockCipher cipher; + + private byte[] oneByte = new byte[1]; + + /** + * basic constructor. + * + * @param cipher the block cipher to be wrapped. + * @exception IllegalArgumentException if the cipher has a block size other than + * one. + */ + public StreamBlockCipher( + BlockCipher cipher) + { + if (cipher.getBlockSize() != 1) + { + throw new IllegalArgumentException("block cipher block size != 1."); + } + + this.cipher = cipher; + } + + /** + * initialise the underlying cipher. + * + * @param forEncryption true if we are setting up for encryption, false otherwise. + * @param param the necessary parameters for the underlying cipher to be initialised. + */ + public void init( + boolean forEncryption, + CipherParameters params) + { + cipher.init(forEncryption, params); + } + + /** + * return the name of the algorithm we are wrapping. + * + * @return the name of the algorithm we are wrapping. + */ + public String getAlgorithmName() + { + return cipher.getAlgorithmName(); + } + + /** + * encrypt/decrypt a single byte returning the result. + * + * @param in the byte to be processed. + * @return the result of processing the input byte. + */ + public byte returnByte( + byte in) + { + oneByte[0] = in; + + cipher.processBlock(oneByte, 0, oneByte, 0); + + return oneByte[0]; + } + + /** + * process a block of bytes from in putting the result into out. + * + * @param in the input byte array. + * @param inOff the offset into the in array where the data to be processed starts. + * @param len the number of bytes to be processed. + * @param out the output buffer the processed bytes go into. + * @param outOff the offset into the output byte array the processed data stars at. + * @exception DataLengthException if the output buffer is too small. + */ + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff) + throws DataLengthException + { + if (outOff + len > out.length) + { + throw new DataLengthException("output buffer too small in processBytes()"); + } + + for (int i = 0; i != len; i++) + { + cipher.processBlock(in, inOff + i, out, outOff + i); + } + } + + /** + * reset the underlying cipher. This leaves it in the same state + * it was at after the last init (if there was one). + */ + public void reset() + { + cipher.reset(); + } +} diff --git a/src/org/bouncycastle/crypto/StreamCipher.java b/src/org/bouncycastle/crypto/StreamCipher.java new file mode 100644 index 0000000..6886e8c --- /dev/null +++ b/src/org/bouncycastle/crypto/StreamCipher.java @@ -0,0 +1,53 @@ +package org.bouncycastle.crypto; + +/** + * the interface stream ciphers conform to. + */ +public interface StreamCipher +{ + /** + * Initialise the cipher. + * + * @param forEncryption if true the cipher is initialised for + * encryption, if false for decryption. + * @param param the key and other data required by the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init(boolean forEncryption, CipherParameters params) + throws IllegalArgumentException; + + /** + * Return the name of the algorithm the cipher implements. + * + * @return the name of the algorithm the cipher implements. + */ + public String getAlgorithmName(); + + /** + * encrypt/decrypt a single byte returning the result. + * + * @param in the byte to be processed. + * @return the result of processing the input byte. + */ + public byte returnByte(byte in); + + /** + * process a block of bytes from in putting the result into out. + * + * @param in the input byte array. + * @param inOff the offset into the in array where the data to be processed starts. + * @param len the number of bytes to be processed. + * @param out the output buffer the processed bytes go into. + * @param outOff the offset into the output byte array the processed data stars at. + * @exception DataLengthException if the output buffer is too small. + */ + public void processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) + throws DataLengthException; + + /** + * reset the cipher. This leaves it in the same state + * it was at after the last init (if there was one). + */ + public void reset(); +} diff --git a/src/org/bouncycastle/crypto/digests/GeneralDigest.java b/src/org/bouncycastle/crypto/digests/GeneralDigest.java new file mode 100644 index 0000000..2d319b7 --- /dev/null +++ b/src/org/bouncycastle/crypto/digests/GeneralDigest.java @@ -0,0 +1,128 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.Digest; + +/** + * base implementation of MD4 family style digest as outlined in + * "Handbook of Applied Cryptography", pages 344 - 347. + */ +public abstract class GeneralDigest + implements Digest +{ + private byte[] xBuf; + private int xBufOff; + + private long byteCount; + + /** + * Standard constructor + */ + protected GeneralDigest() + { + xBuf = new byte[4]; + xBufOff = 0; + } + + /** + * Copy constructor. We are using copy constructors in place + * of the Object.clone() interface as this interface is not + * supported by J2ME. + */ + protected GeneralDigest(GeneralDigest t) + { + xBuf = new byte[t.xBuf.length]; + System.arraycopy(t.xBuf, 0, xBuf, 0, t.xBuf.length); + + xBufOff = t.xBufOff; + byteCount = t.byteCount; + } + + public void update( + byte in) + { + xBuf[xBufOff++] = in; + + if (xBufOff == xBuf.length) + { + processWord(xBuf, 0); + xBufOff = 0; + } + + byteCount++; + } + + public void update( + byte[] in, + int inOff, + int len) + { + // + // fill the current word + // + while ((xBufOff != 0) && (len > 0)) + { + update(in[inOff]); + + inOff++; + len--; + } + + // + // process whole words. + // + while (len > xBuf.length) + { + processWord(in, inOff); + + inOff += xBuf.length; + len -= xBuf.length; + byteCount += xBuf.length; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + + inOff++; + len--; + } + } + + public void finish() + { + long bitLength = (byteCount << 3); + + // + // add the pad bytes. + // + update((byte)128); + + while (xBufOff != 0) + { + update((byte)0); + } + + processLength(bitLength); + + processBlock(); + } + + public void reset() + { + byteCount = 0; + + xBufOff = 0; + for ( int i = 0; i < xBuf.length; i++ ) { + xBuf[i] = 0; + } + } + + protected abstract void processWord(byte[] in, int inOff); + + protected abstract void processLength(long bitLength); + + protected abstract void processBlock(); +} diff --git a/src/org/bouncycastle/crypto/digests/MD2Digest.java b/src/org/bouncycastle/crypto/digests/MD2Digest.java new file mode 100644 index 0000000..88aa6a4 --- /dev/null +++ b/src/org/bouncycastle/crypto/digests/MD2Digest.java @@ -0,0 +1,232 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.Digest; +/** + * implementation of MD2 + * as outlined in RFC1319 by B.Kaliski from RSA Laboratories April 1992 + * + * @author Michael Lee + */ +public class MD2Digest + implements Digest +{ + private static final int DIGEST_LENGTH = 16; + + /* X buffer */ + private byte[] X = new byte[48]; + private int xOff; + /* M buffer */ + private byte[] M = new byte[16]; + private int mOff; + /* check sum */ + private byte[] C = new byte[16]; + private int COff; + + public MD2Digest() + { + reset(); + } + public MD2Digest(MD2Digest t) + { + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + System.arraycopy(t.M, 0, M, 0, t.M.length); + mOff = t.mOff; + System.arraycopy(t.C, 0, C, 0, t.C.length); + COff = t.COff; + } + /** + * return the algorithm name + * + * @return the algorithm name + */ + public String getAlgorithmName() + { + return "MD2"; + } + /** + * return the size, in bytes, of the digest produced by this message digest. + * + * @return the size, in bytes, of the digest produced by this message digest. + */ + public int getDigestSize() + { + return DIGEST_LENGTH; + } + /** + * close the digest, producing the final digest value. The doFinal + * call leaves the digest reset. + * + * @param out the array the digest is to be copied into. + * @param outOff the offset into the out array the digest is to start at. + */ + public int doFinal(byte[] out, int outOff) + { + // add padding + byte paddingByte = (byte)(M.length-mOff); + for (int i=mOff;i 0)) + { + update(in[inOff]); + inOff++; + len--; + } + + // + // process whole words. + // + while (len > 16) + { + System.arraycopy(in,inOff,M,0,16); + processCheckSum(M); + processBlock(M); + len -= 16; + inOff += 16; + } + + // + // load in the remainder. + // + while (len > 0) + { + update(in[inOff]); + inOff++; + len--; + } + } + protected void processCheckSum(byte[] m) + { + int L = C[15]; + for (int i=0;i<16;i++) + { + C[i] ^= S[(m[i] ^ L) & 0xff]; + L = C[i]; + } + } + protected void processBlock(byte[] m) + { + for (int i=0;i<16;i++) + { + X[i+16] = m[i]; + X[i+32] = (byte)(m[i] ^ X[i]); + } + // encrypt block + int t = 0; + + for (int j=0;j<18;j++) + { + for (int k=0;k<48;k++) + { + t = X[k] ^= S[t]; + t = t & 0xff; + } + t = (t + j)%256; + } + } + // 256-byte random permutation constructed from the digits of PI + private static final byte[] S = { + (byte)41,(byte)46,(byte)67,(byte)201,(byte)162,(byte)216,(byte)124, + (byte)1,(byte)61,(byte)54,(byte)84,(byte)161,(byte)236,(byte)240, + (byte)6,(byte)19,(byte)98,(byte)167,(byte)5,(byte)243,(byte)192, + (byte)199,(byte)115,(byte)140,(byte)152,(byte)147,(byte)43,(byte)217, + (byte)188,(byte)76,(byte)130,(byte)202,(byte)30,(byte)155,(byte)87, + (byte)60,(byte)253,(byte)212,(byte)224,(byte)22,(byte)103,(byte)66, + (byte)111,(byte)24,(byte)138,(byte)23,(byte)229,(byte)18,(byte)190, + (byte)78,(byte)196,(byte)214,(byte)218,(byte)158,(byte)222,(byte)73, + (byte)160,(byte)251,(byte)245,(byte)142,(byte)187,(byte)47,(byte)238, + (byte)122,(byte)169,(byte)104,(byte)121,(byte)145,(byte)21,(byte)178, + (byte)7,(byte)63,(byte)148,(byte)194,(byte)16,(byte)137,(byte)11, + (byte)34,(byte)95,(byte)33,(byte)128,(byte)127,(byte)93,(byte)154, + (byte)90,(byte)144,(byte)50,(byte)39,(byte)53,(byte)62,(byte)204, + (byte)231,(byte)191,(byte)247,(byte)151,(byte)3,(byte)255,(byte)25, + (byte)48,(byte)179,(byte)72,(byte)165,(byte)181,(byte)209,(byte)215, + (byte)94,(byte)146,(byte)42,(byte)172,(byte)86,(byte)170,(byte)198, + (byte)79,(byte)184,(byte)56,(byte)210,(byte)150,(byte)164,(byte)125, + (byte)182,(byte)118,(byte)252,(byte)107,(byte)226,(byte)156,(byte)116, + (byte)4,(byte)241,(byte)69,(byte)157,(byte)112,(byte)89,(byte)100, + (byte)113,(byte)135,(byte)32,(byte)134,(byte)91,(byte)207,(byte)101, + (byte)230,(byte)45,(byte)168,(byte)2,(byte)27,(byte)96,(byte)37, + (byte)173,(byte)174,(byte)176,(byte)185,(byte)246,(byte)28,(byte)70, + (byte)97,(byte)105,(byte)52,(byte)64,(byte)126,(byte)15,(byte)85, + (byte)71,(byte)163,(byte)35,(byte)221,(byte)81,(byte)175,(byte)58, + (byte)195,(byte)92,(byte)249,(byte)206,(byte)186,(byte)197,(byte)234, + (byte)38,(byte)44,(byte)83,(byte)13,(byte)110,(byte)133,(byte)40, + (byte)132, 9,(byte)211,(byte)223,(byte)205,(byte)244,(byte)65, + (byte)129,(byte)77,(byte)82,(byte)106,(byte)220,(byte)55,(byte)200, + (byte)108,(byte)193,(byte)171,(byte)250,(byte)36,(byte)225,(byte)123, + (byte)8,(byte)12,(byte)189,(byte)177,(byte)74,(byte)120,(byte)136, + (byte)149,(byte)139,(byte)227,(byte)99,(byte)232,(byte)109,(byte)233, + (byte)203,(byte)213,(byte)254,(byte)59,(byte)0,(byte)29,(byte)57, + (byte)242,(byte)239,(byte)183,(byte)14,(byte)102,(byte)88,(byte)208, + (byte)228,(byte)166,(byte)119,(byte)114,(byte)248,(byte)235,(byte)117, + (byte)75,(byte)10,(byte)49,(byte)68,(byte)80,(byte)180,(byte)143, + (byte)237,(byte)31,(byte)26,(byte)219,(byte)153,(byte)141,(byte)51, + (byte)159,(byte)17,(byte)131,(byte)20 + }; +} diff --git a/src/org/bouncycastle/crypto/digests/MD5Digest.java b/src/org/bouncycastle/crypto/digests/MD5Digest.java new file mode 100644 index 0000000..7325fba --- /dev/null +++ b/src/org/bouncycastle/crypto/digests/MD5Digest.java @@ -0,0 +1,303 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.Digest; + +/** + * implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347. + */ +public class MD5Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 16; + + private int H1, H2, H3, H4; // IV's + + private int[] X = new int[16]; + private int xOff; + + /** + * Standard constructor + */ + public MD5Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public MD5Digest(MD5Digest t) + { + super(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "MD5"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8) + | ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24); + + if (xOff == 16) + { + processBlock(); + } + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength & 0xffffffff); + X[15] = (int)(bitLength >>> 32); + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)word; + out[outOff + 1] = (byte)(word >>> 8); + out[outOff + 2] = (byte)(word >>> 16); + out[outOff + 3] = (byte)(word >>> 24); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H1, out, outOff); + unpackWord(H2, out, outOff + 4); + unpackWord(H3, out, outOff + 8); + unpackWord(H4, out, outOff + 12); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables to the IV values. + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + + xOff = 0; + + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // round 1 left rotates + // + private static final int S11 = 7; + private static final int S12 = 12; + private static final int S13 = 17; + private static final int S14 = 22; + + // + // round 2 left rotates + // + private static final int S21 = 5; + private static final int S22 = 9; + private static final int S23 = 14; + private static final int S24 = 20; + + // + // round 3 left rotates + // + private static final int S31 = 4; + private static final int S32 = 11; + private static final int S33 = 16; + private static final int S34 = 23; + + // + // round 4 left rotates + // + private static final int S41 = 6; + private static final int S42 = 10; + private static final int S43 = 15; + private static final int S44 = 21; + + /* + * rotate int x left n bits. + */ + private int rotateLeft( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + /* + * F, G, H and I are the basic MD5 functions. + */ + private int F( + int u, + int v, + int w) + { + return (u & v) | (~u & w); + } + + private int G( + int u, + int v, + int w) + { + return (u & w) | (v & ~w); + } + + private int H( + int u, + int v, + int w) + { + return u ^ v ^ w; + } + + private int K( + int u, + int v, + int w) + { + return v ^ (u | ~w); + } + + protected void processBlock() + { + int a = H1; + int b = H2; + int c = H3; + int d = H4; + + // + // Round 1 - F cycle, 16 times. + // + a = rotateLeft((a + F(b, c, d) + X[ 0] + 0xd76aa478), S11) + b; + d = rotateLeft((d + F(a, b, c) + X[ 1] + 0xe8c7b756), S12) + a; + c = rotateLeft((c + F(d, a, b) + X[ 2] + 0x242070db), S13) + d; + b = rotateLeft((b + F(c, d, a) + X[ 3] + 0xc1bdceee), S14) + c; + a = rotateLeft((a + F(b, c, d) + X[ 4] + 0xf57c0faf), S11) + b; + d = rotateLeft((d + F(a, b, c) + X[ 5] + 0x4787c62a), S12) + a; + c = rotateLeft((c + F(d, a, b) + X[ 6] + 0xa8304613), S13) + d; + b = rotateLeft((b + F(c, d, a) + X[ 7] + 0xfd469501), S14) + c; + a = rotateLeft((a + F(b, c, d) + X[ 8] + 0x698098d8), S11) + b; + d = rotateLeft((d + F(a, b, c) + X[ 9] + 0x8b44f7af), S12) + a; + c = rotateLeft((c + F(d, a, b) + X[10] + 0xffff5bb1), S13) + d; + b = rotateLeft((b + F(c, d, a) + X[11] + 0x895cd7be), S14) + c; + a = rotateLeft((a + F(b, c, d) + X[12] + 0x6b901122), S11) + b; + d = rotateLeft((d + F(a, b, c) + X[13] + 0xfd987193), S12) + a; + c = rotateLeft((c + F(d, a, b) + X[14] + 0xa679438e), S13) + d; + b = rotateLeft((b + F(c, d, a) + X[15] + 0x49b40821), S14) + c; + + // + // Round 2 - G cycle, 16 times. + // + a = rotateLeft((a + G(b, c, d) + X[ 1] + 0xf61e2562), S21) + b; + d = rotateLeft((d + G(a, b, c) + X[ 6] + 0xc040b340), S22) + a; + c = rotateLeft((c + G(d, a, b) + X[11] + 0x265e5a51), S23) + d; + b = rotateLeft((b + G(c, d, a) + X[ 0] + 0xe9b6c7aa), S24) + c; + a = rotateLeft((a + G(b, c, d) + X[ 5] + 0xd62f105d), S21) + b; + d = rotateLeft((d + G(a, b, c) + X[10] + 0x02441453), S22) + a; + c = rotateLeft((c + G(d, a, b) + X[15] + 0xd8a1e681), S23) + d; + b = rotateLeft((b + G(c, d, a) + X[ 4] + 0xe7d3fbc8), S24) + c; + a = rotateLeft((a + G(b, c, d) + X[ 9] + 0x21e1cde6), S21) + b; + d = rotateLeft((d + G(a, b, c) + X[14] + 0xc33707d6), S22) + a; + c = rotateLeft((c + G(d, a, b) + X[ 3] + 0xf4d50d87), S23) + d; + b = rotateLeft((b + G(c, d, a) + X[ 8] + 0x455a14ed), S24) + c; + a = rotateLeft((a + G(b, c, d) + X[13] + 0xa9e3e905), S21) + b; + d = rotateLeft((d + G(a, b, c) + X[ 2] + 0xfcefa3f8), S22) + a; + c = rotateLeft((c + G(d, a, b) + X[ 7] + 0x676f02d9), S23) + d; + b = rotateLeft((b + G(c, d, a) + X[12] + 0x8d2a4c8a), S24) + c; + + // + // Round 3 - H cycle, 16 times. + // + a = rotateLeft((a + H(b, c, d) + X[ 5] + 0xfffa3942), S31) + b; + d = rotateLeft((d + H(a, b, c) + X[ 8] + 0x8771f681), S32) + a; + c = rotateLeft((c + H(d, a, b) + X[11] + 0x6d9d6122), S33) + d; + b = rotateLeft((b + H(c, d, a) + X[14] + 0xfde5380c), S34) + c; + a = rotateLeft((a + H(b, c, d) + X[ 1] + 0xa4beea44), S31) + b; + d = rotateLeft((d + H(a, b, c) + X[ 4] + 0x4bdecfa9), S32) + a; + c = rotateLeft((c + H(d, a, b) + X[ 7] + 0xf6bb4b60), S33) + d; + b = rotateLeft((b + H(c, d, a) + X[10] + 0xbebfbc70), S34) + c; + a = rotateLeft((a + H(b, c, d) + X[13] + 0x289b7ec6), S31) + b; + d = rotateLeft((d + H(a, b, c) + X[ 0] + 0xeaa127fa), S32) + a; + c = rotateLeft((c + H(d, a, b) + X[ 3] + 0xd4ef3085), S33) + d; + b = rotateLeft((b + H(c, d, a) + X[ 6] + 0x04881d05), S34) + c; + a = rotateLeft((a + H(b, c, d) + X[ 9] + 0xd9d4d039), S31) + b; + d = rotateLeft((d + H(a, b, c) + X[12] + 0xe6db99e5), S32) + a; + c = rotateLeft((c + H(d, a, b) + X[15] + 0x1fa27cf8), S33) + d; + b = rotateLeft((b + H(c, d, a) + X[ 2] + 0xc4ac5665), S34) + c; + + // + // Round 4 - K cycle, 16 times. + // + a = rotateLeft((a + K(b, c, d) + X[ 0] + 0xf4292244), S41) + b; + d = rotateLeft((d + K(a, b, c) + X[ 7] + 0x432aff97), S42) + a; + c = rotateLeft((c + K(d, a, b) + X[14] + 0xab9423a7), S43) + d; + b = rotateLeft((b + K(c, d, a) + X[ 5] + 0xfc93a039), S44) + c; + a = rotateLeft((a + K(b, c, d) + X[12] + 0x655b59c3), S41) + b; + d = rotateLeft((d + K(a, b, c) + X[ 3] + 0x8f0ccc92), S42) + a; + c = rotateLeft((c + K(d, a, b) + X[10] + 0xffeff47d), S43) + d; + b = rotateLeft((b + K(c, d, a) + X[ 1] + 0x85845dd1), S44) + c; + a = rotateLeft((a + K(b, c, d) + X[ 8] + 0x6fa87e4f), S41) + b; + d = rotateLeft((d + K(a, b, c) + X[15] + 0xfe2ce6e0), S42) + a; + c = rotateLeft((c + K(d, a, b) + X[ 6] + 0xa3014314), S43) + d; + b = rotateLeft((b + K(c, d, a) + X[13] + 0x4e0811a1), S44) + c; + a = rotateLeft((a + K(b, c, d) + X[ 4] + 0xf7537e82), S41) + b; + d = rotateLeft((d + K(a, b, c) + X[11] + 0xbd3af235), S42) + a; + c = rotateLeft((c + K(d, a, b) + X[ 2] + 0x2ad7d2bb), S43) + d; + b = rotateLeft((b + K(c, d, a) + X[ 9] + 0xeb86d391), S44) + c; + + H1 += a; + H2 += b; + H3 += c; + H4 += d; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } +} diff --git a/src/org/bouncycastle/crypto/digests/SHA1Digest.java b/src/org/bouncycastle/crypto/digests/SHA1Digest.java new file mode 100644 index 0000000..7a7d176 --- /dev/null +++ b/src/org/bouncycastle/crypto/digests/SHA1Digest.java @@ -0,0 +1,259 @@ +package org.bouncycastle.crypto.digests; + +import org.bouncycastle.crypto.Digest; + +/** + * implementation of SHA-1 as outlined in "Handbook of Applied Cryptography", pages 346 - 349. + * + * It is interesting to ponder why the, apart from the extra IV, the other difference here from MD5 + * is the "endienness" of the word processing! + */ +public class SHA1Digest + extends GeneralDigest +{ + private static final int DIGEST_LENGTH = 20; + + private int H1, H2, H3, H4, H5; + + private int[] X = new int[80]; + private int xOff; + + /** + * Standard constructor + */ + public SHA1Digest() + { + reset(); + } + + /** + * Copy constructor. This will copy the state of the provided + * message digest. + */ + public SHA1Digest(SHA1Digest t) + { + super(t); + + H1 = t.H1; + H2 = t.H2; + H3 = t.H3; + H4 = t.H4; + H5 = t.H5; + + System.arraycopy(t.X, 0, X, 0, t.X.length); + xOff = t.xOff; + } + + public String getAlgorithmName() + { + return "SHA-1"; + } + + public int getDigestSize() + { + return DIGEST_LENGTH; + } + + protected void processWord( + byte[] in, + int inOff) + { + X[xOff++] = ((in[inOff] & 0xff) << 24) | ((in[inOff + 1] & 0xff) << 16) + | ((in[inOff + 2] & 0xff) << 8) | ((in[inOff + 3] & 0xff)); + + if (xOff == 16) + { + processBlock(); + } + } + + private void unpackWord( + int word, + byte[] out, + int outOff) + { + out[outOff] = (byte)(word >>> 24); + out[outOff + 1] = (byte)(word >>> 16); + out[outOff + 2] = (byte)(word >>> 8); + out[outOff + 3] = (byte)word; + } + + protected void processLength( + long bitLength) + { + if (xOff > 14) + { + processBlock(); + } + + X[14] = (int)(bitLength >>> 32); + X[15] = (int)(bitLength & 0xffffffff); + } + + public int doFinal( + byte[] out, + int outOff) + { + finish(); + + unpackWord(H1, out, outOff); + unpackWord(H2, out, outOff + 4); + unpackWord(H3, out, outOff + 8); + unpackWord(H4, out, outOff + 12); + unpackWord(H5, out, outOff + 16); + + reset(); + + return DIGEST_LENGTH; + } + + /** + * reset the chaining variables + */ + public void reset() + { + super.reset(); + + H1 = 0x67452301; + H2 = 0xefcdab89; + H3 = 0x98badcfe; + H4 = 0x10325476; + H5 = 0xc3d2e1f0; + + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } + + // + // Additive constants + // + private static final int Y1 = 0x5a827999; + private static final int Y2 = 0x6ed9eba1; + private static final int Y3 = 0x8f1bbcdc; + private static final int Y4 = 0xca62c1d6; + + private int f( + int u, + int v, + int w) + { + return ((u & v) | ((~u) & w)); + } + + private int h( + int u, + int v, + int w) + { + return (u ^ v ^ w); + } + + private int g( + int u, + int v, + int w) + { + return ((u & v) | (u & w) | (v & w)); + } + + private int rotateLeft( + int x, + int n) + { + return (x << n) | (x >>> (32 - n)); + } + + protected void processBlock() + { + // + // expand 16 word block into 80 word block. + // + for (int i = 16; i <= 79; i++) + { + X[i] = rotateLeft((X[i - 3] ^ X[i - 8] ^ X[i - 14] ^ X[i - 16]), 1); + } + + // + // set up working variables. + // + int A = H1; + int B = H2; + int C = H3; + int D = H4; + int E = H5; + + // + // round 1 + // + for (int j = 0; j <= 19; j++) + { + int t = rotateLeft(A, 5) + f(B, C, D) + E + X[j] + Y1; + + E = D; + D = C; + C = rotateLeft(B, 30); + B = A; + A = t; + } + + // + // round 2 + // + for (int j = 20; j <= 39; j++) + { + int t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y2; + + E = D; + D = C; + C = rotateLeft(B, 30); + B = A; + A = t; + } + + // + // round 3 + // + for (int j = 40; j <= 59; j++) + { + int t = rotateLeft(A, 5) + g(B, C, D) + E + X[j] + Y3; + + E = D; + D = C; + C = rotateLeft(B, 30); + B = A; + A = t; + } + + // + // round 4 + // + for (int j = 60; j <= 79; j++) + { + int t = rotateLeft(A, 5) + h(B, C, D) + E + X[j] + Y4; + + E = D; + D = C; + C = rotateLeft(B, 30); + B = A; + A = t; + } + + H1 += A; + H2 += B; + H3 += C; + H4 += D; + H5 += E; + + // + // reset the offset and clean out the word buffer. + // + xOff = 0; + for (int i = 0; i != X.length; i++) + { + X[i] = 0; + } + } +} diff --git a/src/org/bouncycastle/crypto/encodings/PKCS1Encoding.java b/src/org/bouncycastle/crypto/encodings/PKCS1Encoding.java new file mode 100644 index 0000000..aa4ee61 --- /dev/null +++ b/src/org/bouncycastle/crypto/encodings/PKCS1Encoding.java @@ -0,0 +1,199 @@ +package org.bouncycastle.crypto.encodings; + +import java.math.BigInteger; +import java.util.Random; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.params.ParametersWithRandom; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; + +/** + * this does your basic PKCS 1 v1.5 padding - whether or not you should be using this + * depends on your application - see PKCS1 Version 2 for details. + */ +public class PKCS1Encoding + implements AsymmetricBlockCipher +{ + private static int HEADER_LENGTH = 10; + + // HACK + //private SecureRandom random; + private Random random = new Random(); + + private AsymmetricBlockCipher engine; + private boolean forEncryption; + private boolean forPrivateKey; + + public PKCS1Encoding( + AsymmetricBlockCipher cipher) + { + this.engine = cipher; + } + + public AsymmetricBlockCipher getUnderlyingCipher() + { + return engine; + } + + public void init( + boolean forEncryption, + CipherParameters param) + { + AsymmetricKeyParameter kParam; + if (param instanceof ParametersWithRandom) + { + ParametersWithRandom rParam = (ParametersWithRandom)param; + + // HACK + //this.random = rParam.getRandom(); + kParam = (AsymmetricKeyParameter)rParam.getParameters(); + } + else + { + // HACK + //this.random = new SecureRandom(); + kParam = (AsymmetricKeyParameter)param; + } + + engine.init(forEncryption, kParam); + + this.forPrivateKey = kParam.isPrivate(); + this.forEncryption = forEncryption; + } + + public int getInputBlockSize() + { + int baseBlockSize = engine.getInputBlockSize(); + + if (forEncryption) + { + return baseBlockSize - HEADER_LENGTH; + } + else + { + return baseBlockSize; + } + } + + public int getOutputBlockSize() + { + int baseBlockSize = engine.getOutputBlockSize(); + + if (forEncryption) + { + return baseBlockSize; + } + else + { + return baseBlockSize - HEADER_LENGTH; + } + } + + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + if (forEncryption) + { + return encodeBlock(in, inOff, inLen); + } + else + { + return decodeBlock(in, inOff, inLen); + } + } + + private byte[] encodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = new byte[engine.getInputBlockSize()]; + + if (forPrivateKey) + { + block[0] = 0x01; // type code 1 + + for (int i = 1; i != block.length - inLen - 1; i++) + { + block[i] = (byte)0xFF; + } + } + else + { + random.nextBytes(block); // random fill + + block[0] = 0x02; // type code 2 + + // + // a zero byte marks the end of the padding, so all + // the pad bytes must be non-zero. + // + for (int i = 1; i != block.length - inLen - 1; i++) + { + while (block[i] == 0) + { + block[i] = (byte)random.nextInt(); + } + } + } + + block[block.length - inLen - 1] = 0x00; // mark the end of the padding + System.arraycopy(in, inOff, block, block.length - inLen, inLen); + + return engine.processBlock(block, 0, block.length); + } + + /** + * @exception InvalidCipherTextException if the decrypted block is not in PKCS1 format. + */ + private byte[] decodeBlock( + byte[] in, + int inOff, + int inLen) + throws InvalidCipherTextException + { + byte[] block = engine.processBlock(in, inOff, inLen); + if (block.length < getOutputBlockSize()) + { + throw new InvalidCipherTextException("block truncated"); + } + + if (block[0] != 1 && block[0] != 2) + { + throw new InvalidCipherTextException("unknown block type"); + } + + // + // find and extract the message block. + // + int start; + + for (start = 1; start != block.length; start++) + { + if (block[start] == 0) + { + break; + } + } + + start++; // data should start at the next byte + + if (start >= block.length || start < HEADER_LENGTH) + { + throw new InvalidCipherTextException("no data in block"); + } + + byte[] result = new byte[block.length - start]; + + System.arraycopy(block, start, result, 0, result.length); + + return result; + } +} diff --git a/src/org/bouncycastle/crypto/engines/RC4Engine.java b/src/org/bouncycastle/crypto/engines/RC4Engine.java new file mode 100644 index 0000000..0adc923 --- /dev/null +++ b/src/org/bouncycastle/crypto/engines/RC4Engine.java @@ -0,0 +1,144 @@ +package org.bouncycastle.crypto.engines; + +import org.bouncycastle.crypto.StreamCipher; +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.params.KeyParameter; + +public class RC4Engine implements StreamCipher +{ + private final static int STATE_LENGTH = 256; + + /* + * variables to hold the state of the RC4 engine + * during encryption and decryption + */ + + private byte[] engineState = null; + private int x = 0; + private int y = 0; + private byte[] workingKey = null; + + /** + * initialise a RC4 cipher. + * + * @param forEncryption whether or not we are for encryption. + * @param params the parameters required to set up the cipher. + * @exception IllegalArgumentException if the params argument is + * inappropriate. + */ + public void init( + boolean forEncryption, + CipherParameters params + ) + { + if (params instanceof KeyParameter) + { + /* + * RC4 encryption and decryption is completely + * symmetrical, so the 'forEncryption' is + * irrelevant. + */ + workingKey = ((KeyParameter)params).getKey(); + setKey(workingKey); + + return; + } + + throw new IllegalArgumentException("invalid parameter passed to RC4 init - " + params.getClass().getName()); + } + + public String getAlgorithmName() + { + return "RC4"; + } + + public byte returnByte(byte in) + { + x = (x + 1) & 0xff; + y = (engineState[x] + y) & 0xff; + + // swap + byte tmp = engineState[x]; + engineState[x] = engineState[y]; + engineState[y] = tmp; + + // xor + return (byte)(in ^ engineState[(engineState[x] + engineState[y]) & 0xff]); + } + + public void processBytes( + byte[] in, + int inOff, + int len, + byte[] out, + int outOff + ) + { + if ((inOff + len) > in.length) + { + throw new DataLengthException("input buffer too short"); + } + + if ((outOff + len) > out.length) + { + throw new DataLengthException("output buffer too short"); + } + + for (int i = 0; i < len ; i++) + { + x = (x + 1) & 0xff; + y = (engineState[x] + y) & 0xff; + + // swap + byte tmp = engineState[x]; + engineState[x] = engineState[y]; + engineState[y] = tmp; + + // xor + out[i+outOff] = (byte)(in[i + inOff] + ^ engineState[(engineState[x] + engineState[y]) & 0xff]); + } + } + + public void reset() + { + setKey(workingKey); + } + + // Private implementation + + private void setKey(byte[] keyBytes) + { + workingKey = keyBytes; + + // System.out.println("the key length is ; "+ workingKey.length); + + x = 0; + y = 0; + + if (engineState == null) + { + engineState = new byte[STATE_LENGTH]; + } + + // reset the state of the engine + for (int i=0; i < STATE_LENGTH; i++) + { + engineState[i] = (byte)i; + } + + int i1 = 0; + int i2 = 0; + + for (int i=0; i < STATE_LENGTH; i++) + { + i2 = ((keyBytes[i1] & 0xff) + engineState[i] + i2) & 0xff; + // do the byte-swap inline + byte tmp = engineState[i]; + engineState[i] = engineState[i2]; + engineState[i2] = tmp; + i1 = (i1+1) % keyBytes.length; + } + } +} diff --git a/src/org/bouncycastle/crypto/engines/RSAEngine.java b/src/org/bouncycastle/crypto/engines/RSAEngine.java new file mode 100644 index 0000000..2cbac54 --- /dev/null +++ b/src/org/bouncycastle/crypto/engines/RSAEngine.java @@ -0,0 +1,195 @@ +package org.bouncycastle.crypto.engines; + +import java.math.BigInteger; + +import org.bouncycastle.crypto.CipherParameters; +import org.bouncycastle.crypto.DataLengthException; +import org.bouncycastle.crypto.AsymmetricBlockCipher; +import org.bouncycastle.crypto.params.AsymmetricKeyParameter; +import org.bouncycastle.crypto.params.RSAKeyParameters; +import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters; + +/** + * this does your basic RSA algorithm. + */ +public class RSAEngine + implements AsymmetricBlockCipher +{ + private RSAKeyParameters key; + private boolean forEncryption; + + /** + * initialise the RSA engine. + * + * @param forEncryption treu if we are encrypting, false otherwise. + * @param param the necessary RSA key parameters. + */ + public void init( + boolean forEncryption, + CipherParameters param) + { + this.key = (RSAKeyParameters)param; + this.forEncryption = forEncryption; + } + + /** + * Return the maximum size for an input block to this engine. + * For RSA this is always one byte less than the key size on + * encryption, and the same length as the key size on decryption. + * + * @return maximum size for an input block. + */ + public int getInputBlockSize() + { + int bitSize = key.getModulus().bitLength(); + + if (forEncryption) + { + if ((bitSize % 8) == 0) + { + return bitSize / 8 - 1; + } + + return bitSize / 8; + } + else + { + return (bitSize + 7) / 8; + } + } + + /** + * Return the maximum size for an output block to this engine. + * For RSA this is always one byte less than the key size on + * decryption, and the same length as the key size on encryption. + * + * @return maximum size for an input block. + */ + public int getOutputBlockSize() + { + int bitSize = key.getModulus().bitLength(); + + if (forEncryption) + { + return ((bitSize - 1) + 7) / 8; + } + else + { + return (bitSize - 7) / 8; + } + } + + /** + * Process a single block using the basic RSA algorithm. + * + * @param in the input array. + * @param inOff the offset into the input buffer where the data starts. + * @param inLen the length of the data to be processed. + * @return the result of the RSA process. + * @exception DataLengthException the input block is too large. + */ + public byte[] processBlock( + byte[] in, + int inOff, + int inLen) + { + if (inLen > (getInputBlockSize() + 1)) + { + throw new DataLengthException("input too large for RSA cipher.\n"); + } + else if (inLen == (getInputBlockSize() + 1) && (in[inOff] & 0x80) != 0) + { + throw new DataLengthException("input too large for RSA cipher.\n"); + } + + byte[] block; + + if (inOff != 0 || inLen != in.length) + { + block = new byte[inLen]; + + System.arraycopy(in, inOff, block, 0, inLen); + } + else + { + block = in; + } + + BigInteger input = new BigInteger(1, block); + byte[] output; + + if (key instanceof RSAPrivateCrtKeyParameters) + { + // + // we have the extra factors, use the Chinese Remainder Theorem - the author + // wishes to express his thanks to Dirk Bonekaemper at rtsffm.com for + // advice regarding the expression of this. + // + RSAPrivateCrtKeyParameters crtKey = (RSAPrivateCrtKeyParameters)key; + + BigInteger d = crtKey.getExponent(); + BigInteger p = crtKey.getP(); + BigInteger q = crtKey.getQ(); + BigInteger dP = crtKey.getDP(); + BigInteger dQ = crtKey.getDQ(); + BigInteger qInv = crtKey.getQInv(); + + BigInteger mP, mQ, h, m; + + // mP = ((input mod p) ^ dP)) mod p + mP = (input.remainder(p)).modPow(dP, p); + + // mQ = ((input mod q) ^ dQ)) mod q + mQ = (input.remainder(q)).modPow(dQ, q); + + // h = qInv * (mP - mQ) mod p + h = mP.subtract(mQ); + h = h.multiply(qInv); + h = h.mod(p); // mod (in Java) returns the positive residual + + // m = h * q + mQ + m = h.multiply(q); + m = m.add(mQ); + + output = m.toByteArray(); + } + else + { + output = input.modPow( + key.getExponent(), key.getModulus()).toByteArray(); + } + + if (forEncryption) + { + if (output[0] == 0 && output.length > getOutputBlockSize()) // have ended up with an extra zero byte, copy down. + { + byte[] tmp = new byte[output.length - 1]; + + System.arraycopy(output, 1, tmp, 0, tmp.length); + + return tmp; + } + + if (output.length < getOutputBlockSize()) // have ended up with less bytes than normal, lengthen + { + byte[] tmp = new byte[getOutputBlockSize()]; + + System.arraycopy(output, 0, tmp, tmp.length - output.length, output.length); + + return tmp; + } + } + else + { + if (output[0] == 0) // have ended up with an extra zero byte, copy down. + { + byte[] tmp = new byte[output.length - 1]; + + System.arraycopy(output, 1, tmp, 0, tmp.length); + + return tmp; + } + } + return output; + } +} diff --git a/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java b/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java new file mode 100644 index 0000000..5710395 --- /dev/null +++ b/src/org/bouncycastle/crypto/params/AsymmetricKeyParameter.java @@ -0,0 +1,20 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class AsymmetricKeyParameter + implements CipherParameters +{ + boolean privateKey; + + public AsymmetricKeyParameter( + boolean privateKey) + { + this.privateKey = privateKey; + } + + public boolean isPrivate() + { + return privateKey; + } +} diff --git a/src/org/bouncycastle/crypto/params/KeyParameter.java b/src/org/bouncycastle/crypto/params/KeyParameter.java new file mode 100644 index 0000000..5a1c39d --- /dev/null +++ b/src/org/bouncycastle/crypto/params/KeyParameter.java @@ -0,0 +1,30 @@ +package org.bouncycastle.crypto.params; + +import org.bouncycastle.crypto.CipherParameters; + +public class KeyParameter + implements CipherParameters +{ + private byte[] key; + + public KeyParameter( + byte[] key) + { + this(key, 0, key.length); + } + + public KeyParameter( + byte[] key, + int keyOff, + int keyLen) + { + this.key = new byte[keyLen]; + + System.arraycopy(key, keyOff, this.key, 0, keyLen); + } + + public byte[] getKey() + { + return key; + } +} diff --git a/src/org/bouncycastle/crypto/params/ParametersWithRandom.java b/src/org/bouncycastle/crypto/params/ParametersWithRandom.java new file mode 100644 index 0000000..f8b7c82 --- /dev/null +++ b/src/org/bouncycastle/crypto/params/ParametersWithRandom.java @@ -0,0 +1,41 @@ +package org.bouncycastle.crypto.params; + +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; + +public class ParametersWithRandom + implements CipherParameters +{ + private SecureRandom random; + private CipherParameters parameters; + + public ParametersWithRandom( + CipherParameters parameters, + SecureRandom random) + { + this.random = random; + this.parameters = parameters; + } + + public ParametersWithRandom( + CipherParameters parameters) + { + this.random = null; + this.parameters = parameters; + } + + public SecureRandom getRandom() + { + if (random == null) + { + random = new SecureRandom(); + } + return random; + } + + public CipherParameters getParameters() + { + return parameters; + } +} diff --git a/src/org/bouncycastle/crypto/params/RSAKeyParameters.java b/src/org/bouncycastle/crypto/params/RSAKeyParameters.java new file mode 100644 index 0000000..130678d --- /dev/null +++ b/src/org/bouncycastle/crypto/params/RSAKeyParameters.java @@ -0,0 +1,34 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.crypto.CipherParameters; + +public class RSAKeyParameters + extends AsymmetricKeyParameter +{ + private BigInteger modulus; + private BigInteger exponent; + + public RSAKeyParameters( + boolean isPrivate, + BigInteger modulus, + BigInteger exponent) + { + super(isPrivate); + + this.modulus = modulus; + this.exponent = exponent; + } + + public BigInteger getModulus() + { + return modulus; + } + + public BigInteger getExponent() + { + return exponent; + } +} diff --git a/src/org/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java b/src/org/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java new file mode 100644 index 0000000..63d299c --- /dev/null +++ b/src/org/bouncycastle/crypto/params/RSAPrivateCrtKeyParameters.java @@ -0,0 +1,68 @@ +package org.bouncycastle.crypto.params; + +import java.math.BigInteger; +import java.security.SecureRandom; + +public class RSAPrivateCrtKeyParameters + extends RSAKeyParameters +{ + private BigInteger e; + private BigInteger p; + private BigInteger q; + private BigInteger dP; + private BigInteger dQ; + private BigInteger qInv; + + /** + * + */ + public RSAPrivateCrtKeyParameters( + BigInteger modulus, + BigInteger publicExponent, + BigInteger privateExponent, + BigInteger p, + BigInteger q, + BigInteger dP, + BigInteger dQ, + BigInteger qInv) + { + super(true, modulus, privateExponent); + + this.e = publicExponent; + this.p = p; + this.q = q; + this.dP = dP; + this.dQ = dQ; + this.qInv = qInv; + } + + public BigInteger getPublicExponent() + { + return e; + } + + public BigInteger getP() + { + return p; + } + + public BigInteger getQ() + { + return q; + } + + public BigInteger getDP() + { + return dP; + } + + public BigInteger getDQ() + { + return dQ; + } + + public BigInteger getQInv() + { + return qInv; + } +} diff --git a/src/org/bouncycastle/util/encoders/Base64.java b/src/org/bouncycastle/util/encoders/Base64.java new file mode 100644 index 0000000..423fb30 --- /dev/null +++ b/src/org/bouncycastle/util/encoders/Base64.java @@ -0,0 +1,260 @@ +package org.bouncycastle.util.encoders; + +public class Base64 +{ + private static byte[] encodingTable = + { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', + (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', (byte)'6', + (byte)'7', (byte)'8', (byte)'9', + (byte)'+', (byte)'/' + }; + + /** + * encode the input data producong a base 64 encoded byte array. + * + * @return a byte array containing the base 64 encoded data. + */ + public static byte[] encode( + byte[] data) + { + byte[] bytes; + + if ((data.length % 3) == 0) + { + bytes = new byte[4 * data.length / 3]; + } + else + { + bytes = new byte[4 * ((data.length / 3) + 1)]; + } + + for (int i = 0, j = 0; + i < ((data.length / 3) * 3); i += 3, j += 4) + { + int b1, b2, b3, b4; + int d1, d2, d3; + + d1 = data[i] & 0xff; + d2 = data[i + 1] & 0xff; + d3 = data[i + 2] & 0xff; + + b1 = (d1 >>> 2) & 0x3f; + b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; + b3 = ((d2 << 2) | (d3 >>> 6)) & 0x3f; + b4 = d3 & 0x3f; + + bytes[j] = encodingTable[b1]; + bytes[j + 1] = encodingTable[b2]; + bytes[j + 2] = encodingTable[b3]; + bytes[j + 3] = encodingTable[b4]; + } + + /* + * process the tail end. + */ + int b1, b2, b3; + int d1, d2; + + switch (data.length % 3) + { + case 0: /* nothing left to do */ + break; + case 1: + d1 = data[data.length - 1] & 0xff; + b1 = (d1 >>> 2) & 0x3f; + b2 = (d1 << 4) & 0x3f; + + bytes[bytes.length - 4] = encodingTable[b1]; + bytes[bytes.length - 3] = encodingTable[b2]; + bytes[bytes.length - 2] = (byte)'='; + bytes[bytes.length - 1] = (byte)'='; + break; + case 2: + d1 = data[data.length - 2] & 0xff; + d2 = data[data.length - 1] & 0xff; + + b1 = (d1 >>> 2) & 0x3f; + b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; + b3 = (d2 << 2) & 0x3f; + + bytes[bytes.length - 4] = encodingTable[b1]; + bytes[bytes.length - 3] = encodingTable[b2]; + bytes[bytes.length - 2] = encodingTable[b3]; + bytes[bytes.length - 1] = (byte)'='; + break; + } + + return bytes; + } + + /* + * set up the decoding table. + */ + private static byte[] decodingTable; + + static + { + decodingTable = new byte[128]; + + for (int i = 'A'; i <= 'Z'; i++) + { + decodingTable[i] = (byte)(i - 'A'); + } + + for (int i = 'a'; i <= 'z'; i++) + { + decodingTable[i] = (byte)(i - 'a' + 26); + } + + for (int i = '0'; i <= '9'; i++) + { + decodingTable[i] = (byte)(i - '0' + 52); + } + + decodingTable['+'] = 62; + decodingTable['/'] = 63; + } + + /** + * decode the base 64 encoded input data. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + byte[] data) + { + byte[] bytes; + byte b1, b2, b3, b4; + + if (data[data.length - 2] == '=') + { + bytes = new byte[(((data.length / 4) - 1) * 3) + 1]; + } + else if (data[data.length - 1] == '=') + { + bytes = new byte[(((data.length / 4) - 1) * 3) + 2]; + } + else + { + bytes = new byte[((data.length / 4) * 3)]; + } + + for (int i = 0, j = 0; i < data.length - 4; i += 4, j += 3) + { + b1 = decodingTable[data[i]]; + b2 = decodingTable[data[i + 1]]; + b3 = decodingTable[data[i + 2]]; + b4 = decodingTable[data[i + 3]]; + + bytes[j] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2)); + bytes[j + 2] = (byte)((b3 << 6) | b4); + } + + if (data[data.length - 2] == '=') + { + b1 = decodingTable[data[data.length - 4]]; + b2 = decodingTable[data[data.length - 3]]; + + bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4)); + } + else if (data[data.length - 1] == '=') + { + b1 = decodingTable[data[data.length - 4]]; + b2 = decodingTable[data[data.length - 3]]; + b3 = decodingTable[data[data.length - 2]]; + + bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2)); + } + else + { + b1 = decodingTable[data[data.length - 4]]; + b2 = decodingTable[data[data.length - 3]]; + b3 = decodingTable[data[data.length - 2]]; + b4 = decodingTable[data[data.length - 1]]; + + bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2)); + bytes[bytes.length - 1] = (byte)((b3 << 6) | b4); + } + + return bytes; + } + + /** + * decode the base 64 encoded String data. + * + * @return a byte array representing the decoded data. + */ + public static byte[] decode( + String data) + { + byte[] bytes; + byte b1, b2, b3, b4; + + if (data.charAt(data.length() - 2) == '=') + { + bytes = new byte[(((data.length() / 4) - 1) * 3) + 1]; + } + else if (data.charAt(data.length() - 1) == '=') + { + bytes = new byte[(((data.length() / 4) - 1) * 3) + 2]; + } + else + { + bytes = new byte[((data.length() / 4) * 3)]; + } + + for (int i = 0, j = 0; i < data.length() - 4; i += 4, j += 3) + { + b1 = decodingTable[data.charAt(i)]; + b2 = decodingTable[data.charAt(i + 1)]; + b3 = decodingTable[data.charAt(i + 2)]; + b4 = decodingTable[data.charAt(i + 3)]; + + bytes[j] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[j + 1] = (byte)((b2 << 4) | (b3 >> 2)); + bytes[j + 2] = (byte)((b3 << 6) | b4); + } + + if (data.charAt(data.length() - 2) == '=') + { + b1 = decodingTable[data.charAt(data.length() - 4)]; + b2 = decodingTable[data.charAt(data.length() - 3)]; + + bytes[bytes.length - 1] = (byte)((b1 << 2) | (b2 >> 4)); + } + else if (data.charAt(data.length() - 1) == '=') + { + b1 = decodingTable[data.charAt(data.length() - 4)]; + b2 = decodingTable[data.charAt(data.length() - 3)]; + b3 = decodingTable[data.charAt(data.length() - 2)]; + + bytes[bytes.length - 2] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[bytes.length - 1] = (byte)((b2 << 4) | (b3 >> 2)); + } + else + { + b1 = decodingTable[data.charAt(data.length() - 4)]; + b2 = decodingTable[data.charAt(data.length() - 3)]; + b3 = decodingTable[data.charAt(data.length() - 2)]; + b4 = decodingTable[data.charAt(data.length() - 1)]; + + bytes[bytes.length - 3] = (byte)((b1 << 2) | (b2 >> 4)); + bytes[bytes.length - 2] = (byte)((b2 << 4) | (b3 >> 2)); + bytes[bytes.length - 1] = (byte)((b3 << 6) | b4); + } + + return bytes; + } +} diff --git a/src/org/mozilla/javascript/Arguments.java b/src/org/mozilla/javascript/Arguments.java new file mode 100644 index 0000000..66da72d --- /dev/null +++ b/src/org/mozilla/javascript/Arguments.java @@ -0,0 +1,146 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** + * This class implements the "arguments" object. + * + * See ECMA 10.1.8 + * + * @see org.mozilla.javascript.NativeCall + * @author Norris Boyd + */ +class Arguments extends ScriptableObject { + + public Arguments(NativeCall activation) { + this.activation = activation; + + Scriptable parent = activation.getParentScope(); + setParentScope(parent); + setPrototype(ScriptableObject.getObjectPrototype(parent)); + + args = activation.getOriginalArguments(); + int length = args.length; + Object callee = activation.funObj; + + defineProperty("length", new Integer(length), + ScriptableObject.DONTENUM); + defineProperty("callee", callee, ScriptableObject.DONTENUM); + + hasCaller = (activation.funObj.version <= Context.VERSION_1_3 && + activation.funObj.version != Context.VERSION_DEFAULT); + } + + public String getClassName() { + return "Arguments"; + } + + public boolean has(String name, Scriptable start) { + return (hasCaller && name.equals("caller")) || super.has(name, start); + } + + public boolean has(int index, Scriptable start) { + Object[] args = activation.getOriginalArguments(); + return (0 <= index && index < args.length) || super.has(index, start); + } + + public Object get(String name, Scriptable start) { + if (hasCaller && name.equals("caller")) { + NativeCall caller = activation.caller; + if (caller == null || caller.originalArgs == null) return null; + return caller.get("arguments", caller); + + } else if (name.equals("cascade")) { + return org.xwt.Trap.cascadeFunction; + + } else if (name.equals("trapee")) { + return org.xwt.Trap.currentTrapee(); + } + + return super.get(name, start); + } + + public Object get(int index, Scriptable start) { + if (0 <= index && index < args.length) { + NativeFunction f = activation.funObj; + if (index < f.argCount) + return activation.get(f.argNames[index], activation); + return args[index]; + } + return super.get(index, start); + } + + public void put(String name, Scriptable start, Object value) { + if (name.equals("caller")) { + // Set "hasCaller" to false so that we won't look up a + // computed value. + hasCaller = false; + } + super.put(name, start, value); + } + + public void put(int index, Scriptable start, Object value) { + if (0 <= index && index < args.length) { + NativeFunction f = activation.funObj; + if (index < f.argCount) + activation.put(f.argNames[index], activation, value); + else + args[index] = value; + return; + } + super.put(index, start, value); + } + + public void delete(String name) { + if (name.equals("caller")) + hasCaller = false; + super.delete(name); + } + + public void delete(int index) { + if (0 <= index && index < args.length) { + NativeFunction f = activation.funObj; + if (index < f.argCount) + activation.delete(f.argNames[index]); + else + args[index] = Undefined.instance; + } + } + + private NativeCall activation; + private Object[] args; + private boolean hasCaller; +} diff --git a/src/org/mozilla/javascript/BaseFunction.java b/src/org/mozilla/javascript/BaseFunction.java new file mode 100644 index 0000000..79c4f36 --- /dev/null +++ b/src/org/mozilla/javascript/BaseFunction.java @@ -0,0 +1,521 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Igor Bukanov + * Roger Lawrence + * Mike McCabe + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** + * The base class for Function objects + * See ECMA 15.3. + * @author Norris Boyd + */ +public class BaseFunction extends IdScriptable implements Function { + + static void init(Context cx, Scriptable scope, boolean sealed) { + BaseFunction obj = new BaseFunction(); + obj.prototypeFlag = true; + obj.functionName = ""; + obj.prototypePropertyAttrs = DONTENUM | READONLY | PERMANENT; + obj.addAsPrototype(MAX_PROTOTYPE_ID, cx, scope, sealed); + } + + protected void fillConstructorProperties + (Context cx, IdFunction ctor, boolean sealed) + { + // Fix up bootstrapping problem: getPrototype of the IdFunction + // can not return Function.prototype because Function object is not + // yet defined. + ctor.setPrototype(this); + } + + public String getClassName() { + return "Function"; + } + + /** + * Implements the instanceof operator for JavaScript Function objects. + *

+ * + * foo = new Foo();
+ * foo instanceof Foo; // true
+ *
+ * + * @param instance The value that appeared on the LHS of the instanceof + * operator + * @return true if the "prototype" property of "this" appears in + * value's prototype chain + * + */ + public boolean hasInstance(Scriptable instance) { + Object protoProp = ScriptableObject.getProperty(this, "prototype"); + if (protoProp instanceof Scriptable && protoProp != Undefined.instance) + { + return ScriptRuntime.jsDelegatesTo(instance, (Scriptable)protoProp); + } + throw NativeGlobal.typeError1 + ("msg.instanceof.bad.prototype", functionName, instance); + } + + protected int getIdDefaultAttributes(int id) { + switch (id) { + case Id_length: + case Id_arity: + case Id_name: + return DONTENUM | READONLY | PERMANENT; + case Id_prototype: + return prototypePropertyAttrs; + case Id_arguments: + return EMPTY; + } + return super.getIdDefaultAttributes(id); + } + + protected boolean hasIdValue(int id) { + if (id == Id_prototype) { + return prototypeProperty != NOT_FOUND; + } + else if (id == Id_arguments) { + // Should after delete Function.arguments its activation still + // be available during Function call? + // This code assumes it should not: after default set/deleteIdValue + // hasIdValue/getIdValue would not be called again + // To handle the opposite case, set/deleteIdValue should be + // overwritten as well + return null != getActivation(Context.getContext()); + } + return super.hasIdValue(id); + } + + protected Object getIdValue(int id) { + switch (id) { + case Id_length: return wrap_int(getLength()); + case Id_arity: return wrap_int(getArity()); + case Id_name: return getFunctionName(); + case Id_prototype: return getPrototypeProperty(); + case Id_arguments: return getArguments(); + } + return super.getIdValue(id); + } + + protected void setIdValue(int id, Object value) { + if (id == Id_prototype) { + prototypeProperty = (value != null) ? value : NULL_TAG; + return; + } + super.setIdValue(id, value); + } + + protected void deleteIdValue(int id) { + if (id == Id_prototype) { + prototypeProperty = NOT_FOUND; + return; + } + super.deleteIdValue(id); + } + + public int methodArity(int methodId) { + if (prototypeFlag) { + switch (methodId) { + case Id_constructor: + case Id_toString: + case Id_apply: + case Id_call: + return 1; + } + } + return super.methodArity(methodId); + } + + public Object execMethod(int methodId, IdFunction f, Context cx, + Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + if (prototypeFlag) { + switch (methodId) { + case Id_constructor: + return jsConstructor(cx, scope, args); + + case Id_toString: + return jsFunction_toString(cx, thisObj, args); + + case Id_apply: + return jsFunction_apply(cx, scope, thisObj, args); + + case Id_call: + return jsFunction_call(cx, scope, thisObj, args); + } + } + return super.execMethod(methodId, f, cx, scope, thisObj, args); + } + + /** + * Make value as DontEnum, DontDelete, ReadOnly + * prototype property of this Function object + */ + public void setImmunePrototypeProperty(Object value) { + prototypeProperty = (value != null) ? value : NULL_TAG; + prototypePropertyAttrs = DONTENUM | READONLY | PERMANENT; + } + + protected Scriptable getClassPrototype() { + Object protoVal = getPrototypeProperty(); + if (protoVal == null + || !(protoVal instanceof Scriptable) + || (protoVal == Undefined.instance)) + protoVal = getClassPrototype(this, "Object"); + return (Scriptable) protoVal; + } + + /** + * Should be overridden. + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException + { + return Undefined.instance; + } + + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + throws JavaScriptException + { + Scriptable newInstance = new NativeObject(); + + newInstance.setPrototype(getClassPrototype()); + newInstance.setParentScope(getParentScope()); + + Object val = call(cx, scope, newInstance, args); + if (val instanceof Scriptable && val != Undefined.instance) { + return (Scriptable) val; + } + return newInstance; + } + + /** + * Decompile the source information associated with this js + * function/script back into a string. + * + * @param cx Current context + * + * @param indent How much to indent the decompiled result + * + * @param justbody Whether the decompilation should omit the + * function header and trailing brace. + */ + + public String decompile(Context cx, int indent, boolean justbody) { + StringBuffer sb = new StringBuffer(); + if (!justbody) { + sb.append("function "); + sb.append(getFunctionName()); + sb.append("() {\n\t"); + } + sb.append("[native code, arity="); + sb.append(getArity()); + sb.append("]\n"); + if (!justbody) { + sb.append("}\n"); + } + return sb.toString(); + } + + public int getArity() { return 0; } + + public int getLength() { return 0; } + + public String getFunctionName() { + if (functionName == null) + return ""; + if (functionName.equals("anonymous")) { + Context cx = Context.getCurrentContext(); + if (cx != null && cx.getLanguageVersion() == Context.VERSION_1_2) + return ""; + } + return functionName; + } + + private Object getPrototypeProperty() { + Object result = prototypeProperty; + if (result == null) { + synchronized (this) { + result = prototypeProperty; + if (result == null) { + setupDefaultPrototype(); + result = prototypeProperty; + } + } + } + else if (result == NULL_TAG) { result = null; } + return result; + } + + private void setupDefaultPrototype() { + NativeObject obj = new NativeObject(); + final int attr = ScriptableObject.DONTENUM | + ScriptableObject.READONLY | + ScriptableObject.PERMANENT; + obj.defineProperty("constructor", this, attr); + // put the prototype property into the object now, then in the + // wacky case of a user defining a function Object(), we don't + // get an infinite loop trying to find the prototype. + prototypeProperty = obj; + Scriptable proto = getObjectPrototype(this); + if (proto != obj) { + // not the one we just made, it must remain grounded + obj.setPrototype(proto); + } + } + + private Object getArguments() { + // .arguments is deprecated, so we use a slow + // way of getting it that doesn't add to the invocation cost. + // TODO: add warning, error based on version + NativeCall activation = getActivation(Context.getContext()); + return activation == null + ? null + : activation.get("arguments", activation); + } + + NativeCall getActivation(Context cx) { + NativeCall activation = cx.currentActivation; + while (activation != null) { + if (activation.getFunctionObject() == this) + return activation; + activation = activation.caller; + } + return null; + } + + private static Object jsConstructor(Context cx, Scriptable scope, + Object[] args) + { + int arglen = args.length; + StringBuffer funArgs = new StringBuffer(); + + /* Collect the arguments into a string. */ + + int i; + for (i = 0; i < arglen - 1; i++) { + if (i > 0) + funArgs.append(','); + funArgs.append(ScriptRuntime.toString(args[i])); + } + String funBody = arglen == 0 ? "" : ScriptRuntime.toString(args[i]); + + String source = "function (" + funArgs.toString() + ") {" + + funBody + "}"; + int[] linep = { 0 }; + String filename = Context.getSourcePositionFromStack(linep); + if (filename == null) { + filename = ""; + linep[0] = 1; + } + Object securityDomain = cx.getSecurityDomainForStackDepth(4); + Scriptable global = ScriptableObject.getTopLevelScope(scope); + + // Compile the function with opt level of -1 to force interpreter + // mode. + int oldOptLevel = cx.getOptimizationLevel(); + cx.setOptimizationLevel(-1); + NativeFunction fn; + try { + fn = (NativeFunction) cx.compileFunction(global, source, + filename, linep[0], + securityDomain); + } + finally { cx.setOptimizationLevel(oldOptLevel); } + + fn.functionName = "anonymous"; + fn.setPrototype(getFunctionPrototype(global)); + fn.setParentScope(global); + + return fn; + } + + private static Object jsFunction_toString(Context cx, Scriptable thisObj, + Object[] args) + { + int indent = ScriptRuntime.toInt32(args, 0); + Object val = thisObj.getDefaultValue(ScriptRuntime.FunctionClass); + if (val instanceof BaseFunction) { + return ((BaseFunction)val).decompile(cx, indent, false); + } + throw NativeGlobal.typeError1("msg.incompat.call", "toString", thisObj); + } + + /** + * Function.prototype.apply + * + * A proposed ECMA extension for round 2. + */ + private static Object jsFunction_apply(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + throws JavaScriptException + { + if (args.length != 2) + return jsFunction_call(cx, scope, thisObj, args); + Object val = thisObj.getDefaultValue(ScriptRuntime.FunctionClass); + Scriptable newThis = args[0] == null + ? ScriptableObject.getTopLevelScope(thisObj) + : ScriptRuntime.toObject(scope, args[0]); + Object[] newArgs; + if (args.length > 1) { + if ((args[1] instanceof NativeArray) + || (args[1] instanceof Arguments)) + newArgs = cx.getElements((Scriptable) args[1]); + else + throw NativeGlobal.typeError0("msg.arg.isnt.array", thisObj); + } + else + newArgs = ScriptRuntime.emptyArgs; + return ScriptRuntime.call(cx, val, newThis, newArgs, newThis); + } + + /** + * Function.prototype.call + * + * A proposed ECMA extension for round 2. + */ + private static Object jsFunction_call(Context cx, Scriptable scope, + Scriptable thisObj, Object[] args) + throws JavaScriptException + { + Object val = thisObj.getDefaultValue(ScriptRuntime.FunctionClass); + if (args.length == 0) { + Scriptable s = ScriptRuntime.toObject(scope, val); + Scriptable topScope = s.getParentScope(); + return ScriptRuntime.call(cx, val, + topScope, ScriptRuntime.emptyArgs, + topScope); + } else { + Scriptable newThis = args[0] == null + ? ScriptableObject.getTopLevelScope(thisObj) + : ScriptRuntime.toObject(scope, args[0]); + + Object[] newArgs = new Object[args.length - 1]; + System.arraycopy(args, 1, newArgs, 0, newArgs.length); + return ScriptRuntime.call(cx, val, newThis, newArgs, newThis); + } + } + + protected int maxInstanceId() { return MAX_INSTANCE_ID; } + + protected String getIdName(int id) { + switch (id) { + case Id_length: return "length"; + case Id_arity: return "arity"; + case Id_name: return "name"; + case Id_prototype: return "prototype"; + case Id_arguments: return "arguments"; + } + + if (prototypeFlag) { + switch (id) { + case Id_constructor: return "constructor"; + case Id_toString: return "toString"; + case Id_apply: return "apply"; + case Id_call: return "call"; + } + } + return null; + } + +// #string_id_map# + + private static final int + Id_length = 1, + Id_arity = 2, + Id_name = 3, + Id_prototype = 4, + Id_arguments = 5, + + MAX_INSTANCE_ID = 5; + + protected int mapNameToId(String s) { + int id; +// #generated# Last update: 2001-05-20 00:12:12 GMT+02:00 + L0: { id = 0; String X = null; int c; + L: switch (s.length()) { + case 4: X="name";id=Id_name; break L; + case 5: X="arity";id=Id_arity; break L; + case 6: X="length";id=Id_length; break L; + case 9: c=s.charAt(0); + if (c=='a') { X="arguments";id=Id_arguments; } + else if (c=='p') { X="prototype";id=Id_prototype; } + break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# +// #/string_id_map# + + if (id != 0 || !prototypeFlag) { return id; } + +// #string_id_map# +// #generated# Last update: 2001-05-20 00:12:12 GMT+02:00 + L0: { id = 0; String X = null; + L: switch (s.length()) { + case 4: X="call";id=Id_call; break L; + case 5: X="apply";id=Id_apply; break L; + case 8: X="toString";id=Id_toString; break L; + case 11: X="constructor";id=Id_constructor; break L; + } + if (X!=null && X!=s && !X.equals(s)) id = 0; + } +// #/generated# + return id; + } + + private static final int + Id_constructor = MAX_INSTANCE_ID + 1, + Id_toString = MAX_INSTANCE_ID + 2, + Id_apply = MAX_INSTANCE_ID + 3, + Id_call = MAX_INSTANCE_ID + 4, + + MAX_PROTOTYPE_ID = MAX_INSTANCE_ID + 4; + +// #/string_id_map# + + protected String functionName; + + private Object prototypeProperty; + private int prototypePropertyAttrs = DONTENUM; + + private boolean prototypeFlag; +} + diff --git a/src/org/mozilla/javascript/BinaryDigitReader.java b/src/org/mozilla/javascript/BinaryDigitReader.java new file mode 100644 index 0000000..a6b4771 --- /dev/null +++ b/src/org/mozilla/javascript/BinaryDigitReader.java @@ -0,0 +1,75 @@ + +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Waldemar Horwat + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +package org.mozilla.javascript; + +final class BinaryDigitReader { + int lgBase; // Logarithm of base of number + int digit; // Current digit value in radix given by base + int digitPos; // Bit position of last bit extracted from digit + String digits; // String containing the digits + int start; // Index of the first remaining digit + int end; // Index past the last remaining digit + + BinaryDigitReader(int base, String digits, int start, int end) { + lgBase = 0; + while (base != 1) { + lgBase++; + base >>= 1; + } + digitPos = 0; + this.digits = digits; + this.start = start; + this.end = end; + } + + /* Return the next binary digit from the number or -1 if done */ + int getNextBinaryDigit() + { + if (digitPos == 0) { + if (start == end) + return -1; + + char c = digits.charAt(start++); + if ('0' <= c && c <= '9') + digit = c - '0'; + else if ('a' <= c && c <= 'z') + digit = c - 'a' + 10; + else digit = c - 'A' + 10; + digitPos = lgBase; + } + return digit >> --digitPos & 1; + } +} diff --git a/src/org/mozilla/javascript/ClassDefinitionException.java b/src/org/mozilla/javascript/ClassDefinitionException.java new file mode 100644 index 0000000..3b5a8e7 --- /dev/null +++ b/src/org/mozilla/javascript/ClassDefinitionException.java @@ -0,0 +1,48 @@ + +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +// API class + +package org.mozilla.javascript; + +/** + * Thrown if errors are detected while attempting to define a host object + * from a Java class. + */ +public class ClassDefinitionException extends Exception { + + public ClassDefinitionException(String detail) { + super(detail); + } +} diff --git a/src/org/mozilla/javascript/ClassNameHelper.java b/src/org/mozilla/javascript/ClassNameHelper.java new file mode 100644 index 0000000..70cb600 --- /dev/null +++ b/src/org/mozilla/javascript/ClassNameHelper.java @@ -0,0 +1,63 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * Roger Lawrence + * Andi Vajda + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +public interface ClassNameHelper { + + public String getTargetClassFileName(); + + public void setTargetClassFileName(String classFileName); + + public String getTargetPackage(); + + public void setTargetPackage(String targetPackage); + + public String getTargetClassFileName(String className); + + public String getGeneratingDirectory(); + + public void setTargetExtends(Class extendsClass); + + public void setTargetImplements(Class[] implementsClasses); + + public ClassOutput getClassOutput(); + + public void setClassOutput(ClassOutput classOutput); + + public void reset(); +} diff --git a/src/org/mozilla/javascript/ClassOutput.java b/src/org/mozilla/javascript/ClassOutput.java new file mode 100644 index 0000000..997b89e --- /dev/null +++ b/src/org/mozilla/javascript/ClassOutput.java @@ -0,0 +1,57 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Andi Vajda + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ +package org.mozilla.javascript; + +// API class + +import java.io.*; + +/** + * This interface is implemented by classes interested in the bytecode + * generated by the rhino compiler for script objects. + * + * @see Context + * @author Andi Vajda + */ +public interface ClassOutput { + /** + * @param className the name of the class for which bytecode is ready. + * @param isTopLevel if true, represents the top-level script being compiled + * @return a stream into which to write bytecode. + * @since 1.5 Release 2 + */ + public OutputStream getOutputStream(String className, boolean isTopLevel) + throws IOException; +} diff --git a/src/org/mozilla/javascript/Context.java b/src/org/mozilla/javascript/Context.java new file mode 100644 index 0000000..6e6dfbb --- /dev/null +++ b/src/org/mozilla/javascript/Context.java @@ -0,0 +1,2201 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * + * Patrick Beard + * Norris Boyd + * Igor Bukanov + * Brendan Eich + * Roger Lawrence + * Mike McCabe + * Ian D. Stewart + * Andi Vajda + * Andrew Wason + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +// API class + +package org.mozilla.javascript; + +import java.beans.*; +import java.io.*; +import java.util.Vector; +import java.util.Enumeration; +import java.util.Hashtable; +import java.util.Locale; +import java.util.ResourceBundle; +import java.util.ListResourceBundle; +import java.text.MessageFormat; +import java.lang.reflect.*; +import org.mozilla.javascript.debug.*; + +/** + * This class represents the runtime context of an executing script. + * + * Before executing a script, an instance of Context must be created + * and associated with the thread that will be executing the script. + * The Context will be used to store information about the executing + * of the script such as the call stack. Contexts are associated with + * the current thread using the enter() method.

+ * + * The behavior of the execution engine may be altered through methods + * such as setErrorReporter.

+ * + * Different forms of script execution are supported. Scripts may be + * evaluated from the source directly, or first compiled and then later + * executed. Interactive execution is also supported.

+ * + * Some aspects of script execution, such as type conversions and + * object creation, may be accessed directly through methods of + * Context. + * + * @see Scriptable + * @author Norris Boyd + * @author Brendan Eich + */ + +public class Context { + public static final String languageVersionProperty = "language version"; + public static final String errorReporterProperty = "error reporter"; + + /** + * Create a new Context. + * + * Note that the Context must be associated with a thread before + * it can be used to execute a script. + * + * @see org.mozilla.javascript.Context#enter + */ + public Context() { + init(); + } + + /** + * Create a new context with the associated security support. + * + * @param securitySupport an encapsulation of the functionality + * needed to support security for scripts. + * @see org.mozilla.javascript.SecuritySupport + */ + public Context(SecuritySupport securitySupport) { + this.securitySupport = securitySupport; + init(); + } + + private void init() { + setLanguageVersion(VERSION_DEFAULT); + optimizationLevel = codegenClass != null ? 0 : -1; + Object[] array = contextListeners; + if (array != null) { + for (int i = array.length; i-- != 0;) { + ((ContextListener)array[i]).contextCreated(this); + } + } + } + + /** + * Get a context associated with the current thread, creating + * one if need be. + * + * The Context stores the execution state of the JavaScript + * engine, so it is required that the context be entered + * before execution may begin. Once a thread has entered + * a Context, then getCurrentContext() may be called to find + * the context that is associated with the current thread. + *

+ * Calling enter() will + * return either the Context currently associated with the + * thread, or will create a new context and associate it + * with the current thread. Each call to enter() + * must have a matching call to exit(). For example, + *

+     *      Context cx = Context.enter();
+     *      ...
+     *      cx.evaluateString(...);
+     *      Context.exit();
+     * 
+ * @return a Context associated with the current thread + * @see org.mozilla.javascript.Context#getCurrentContext + * @see org.mozilla.javascript.Context#exit + */ + public static Context enter() { + return enter(null); + } + + /** + * Get a Context associated with the current thread, using + * the given Context if need be. + *

+ * The same as enter() except that cx + * is associated with the current thread and returned if + * the current thread has no associated context and cx + * is not associated with any other thread. + * @param cx a Context to associate with the thread if possible + * @return a Context associated with the current thread + */ + public static Context enter(Context cx) { + // There's some duplication of code in this method to avoid + // unnecessary synchronizations. + Thread t = Thread.currentThread(); + Context current = (Context) threadContexts.get(t); + if (current != null) { + synchronized (current) { + current.enterCount++; + } + } + else if (cx != null) { + synchronized (cx) { + if (cx.currentThread == null) { + cx.currentThread = t; + threadContexts.put(t, cx); + cx.enterCount++; + } + } + current = cx; + } + else { + current = new Context(); + current.currentThread = t; + threadContexts.put(t, current); + current.enterCount = 1; + } + Object[] array = contextListeners; + if (array != null) { + for (int i = array.length; i-- != 0;) { + ((ContextListener)array[i]).contextEntered(current); + } + } + return current; + } + + /** + * Exit a block of code requiring a Context. + * + * Calling exit() will remove the association between + * the current thread and a Context if the prior call to + * enter() on this thread newly associated a Context + * with this thread. + * Once the current thread no longer has an associated Context, + * it cannot be used to execute JavaScript until it is again associated + * with a Context. + * + * @see org.mozilla.javascript.Context#enter + */ + public static void exit() { + Context cx = getCurrentContext(); + boolean released = false; + if (cx != null) { + synchronized (cx) { + if (--cx.enterCount == 0) { + threadContexts.remove(cx.currentThread); + cx.currentThread = null; + released = true; + } + } + Object[] array = contextListeners; + if (array != null) { + for (int i = array.length; i-- != 0;) { + ContextListener l = (ContextListener)array[i]; + l.contextExited(cx); + if (released) { l.contextReleased(cx); } + } + } + } + } + + /** + * Get the current Context. + * + * The current Context is per-thread; this method looks up + * the Context associated with the current thread.

+ * + * @return the Context associated with the current thread, or + * null if no context is associated with the current + * thread. + * @see org.mozilla.javascript.Context#enter + * @see org.mozilla.javascript.Context#exit + */ + public static Context getCurrentContext() { + Thread t = Thread.currentThread(); + return (Context) threadContexts.get(t); + } + + public static Context getContextForThread(Thread t) { + Context ret = (Context) threadContexts.get(t); + return ret == null ? Context.enter() : ret; + } + + /** + * Language versions + * + * All integral values are reserved for future version numbers. + */ + + /** + * The unknown version. + */ + public static final int VERSION_UNKNOWN = -1; + + /** + * The default version. + */ + public static final int VERSION_DEFAULT = 0; + + /** + * JavaScript 1.0 + */ + public static final int VERSION_1_0 = 100; + + /** + * JavaScript 1.1 + */ + public static final int VERSION_1_1 = 110; + + /** + * JavaScript 1.2 + */ + public static final int VERSION_1_2 = 120; + + /** + * JavaScript 1.3 + */ + public static final int VERSION_1_3 = 130; + + /** + * JavaScript 1.4 + */ + public static final int VERSION_1_4 = 140; + + /** + * JavaScript 1.5 + */ + public static final int VERSION_1_5 = 150; + + /** + * Get the current language version. + *

+ * The language version number affects JavaScript semantics as detailed + * in the overview documentation. + * + * @return an integer that is one of VERSION_1_0, VERSION_1_1, etc. + */ + public int getLanguageVersion() { + return version; + } + + /** + * Set the language version. + * + *

+ * Setting the language version will affect functions and scripts compiled + * subsequently. See the overview documentation for version-specific + * behavior. + * + * @param version the version as specified by VERSION_1_0, VERSION_1_1, etc. + */ + public void setLanguageVersion(int version) { + Object[] array = listeners; + if (array != null && version != this.version) { + firePropertyChangeImpl(array, languageVersionProperty, + new Integer(this.version), + new Integer(version)); + } + this.version = version; + } + + /** + * Get the implementation version. + * + *

+ * The implementation version is of the form + *

+     *    "name langVer release relNum date"
+     * 
+ * where name is the name of the product, langVer is + * the language version, relNum is the release number, and + * date is the release date for that specific + * release in the form "yyyy mm dd". + * + * @return a string that encodes the product, language version, release + * number, and date. + */ + public String getImplementationVersion() { + return "Rhino 1.5 release 2 2001 07 27"; + } + + /** + * Get the current error reporter. + * + * @see org.mozilla.javascript.ErrorReporter + */ + public ErrorReporter getErrorReporter() { + if (errorReporter == null) { + errorReporter = new DefaultErrorReporter(); + } + return errorReporter; + } + + /** + * Change the current error reporter. + * + * @return the previous error reporter + * @see org.mozilla.javascript.ErrorReporter + */ + public ErrorReporter setErrorReporter(ErrorReporter reporter) { + ErrorReporter result = errorReporter; + Object[] array = listeners; + if (array != null && errorReporter != reporter) { + firePropertyChangeImpl(array, errorReporterProperty, + errorReporter, reporter); + } + errorReporter = reporter; + return result; + } + + /** + * Get the current locale. Returns the default locale if none has + * been set. + * + * @see java.util.Locale + */ + + public Locale getLocale() { + if (locale == null) + locale = Locale.getDefault(); + return locale; + } + + /** + * Set the current locale. + * + * @see java.util.Locale + */ + public Locale setLocale(Locale loc) { + Locale result = locale; + locale = loc; + return result; + } + + /** + * Register an object to receive notifications when a bound property + * has changed + * @see java.beans.PropertyChangeEvent + * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) + * @param listener the listener + */ + public void addPropertyChangeListener(PropertyChangeListener listener) { + synchronized (this) { + listeners = ListenerArray.add(listeners, listener); + } + } + + /** + * Remove an object from the list of objects registered to receive + * notification of changes to a bounded property + * @see java.beans.PropertyChangeEvent + * @see #addPropertyChangeListener(java.beans.PropertyChangeListener) + * @param listener the listener + */ + public void removePropertyChangeListener(PropertyChangeListener listener) { + synchronized (this) { + listeners = ListenerArray.remove(listeners, listener); + } + } + + /** + * Notify any registered listeners that a bounded property has changed + * @see #addPropertyChangeListener(java.beans.PropertyChangeListener) + * @see #removePropertyChangeListener(java.beans.PropertyChangeListener) + * @see java.beans.PropertyChangeListener + * @see java.beans.PropertyChangeEvent + * @param property the bound property + * @param oldValue the old value + * @param newVale the new value + */ + void firePropertyChange(String property, Object oldValue, + Object newValue) + { + Object[] array = listeners; + if (array != null) { + firePropertyChangeImpl(array, property, oldValue, newValue); + } + } + + private void firePropertyChangeImpl(Object[] array, String property, + Object oldValue, Object newValue) + { + for (int i = array.length; i-- != 0;) { + Object obj = array[i]; + if (obj instanceof PropertyChangeListener) { + PropertyChangeListener l = (PropertyChangeListener)obj; + l.propertyChange(new PropertyChangeEvent( + this, property, oldValue, newValue)); + } + } + } + + /** + * Report a warning using the error reporter for the current thread. + * + * @param message the warning message to report + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportWarning(String message, String sourceName, + int lineno, String lineSource, + int lineOffset) + { + Context cx = Context.getContext(); + cx.getErrorReporter().warning(message, sourceName, lineno, + lineSource, lineOffset); + } + + /** + * Report a warning using the error reporter for the current thread. + * + * @param message the warning message to report + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportWarning(String message) { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + Context.reportWarning(message, filename, linep[0], null, 0); + } + + /** + * Report an error using the error reporter for the current thread. + * + * @param message the error message to report + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportError(String message, String sourceName, + int lineno, String lineSource, + int lineOffset) + { + Context cx = getCurrentContext(); + if (cx != null) { + cx.errorCount++; + cx.getErrorReporter().error(message, sourceName, lineno, + lineSource, lineOffset); + } else { + throw new EvaluatorException(message); + } + } + + /** + * Report an error using the error reporter for the current thread. + * + * @param message the error message to report + * @see org.mozilla.javascript.ErrorReporter + */ + public static void reportError(String message) { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + Context.reportError(message, filename, linep[0], null, 0); + } + + /** + * Report a runtime error using the error reporter for the current thread. + * + * @param message the error message to report + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param lineSource the text of the line (may be null) + * @param lineOffset the offset into lineSource where problem was detected + * @return a runtime exception that will be thrown to terminate the + * execution of the script + * @see org.mozilla.javascript.ErrorReporter + */ + public static EvaluatorException reportRuntimeError(String message, + String sourceName, + int lineno, + String lineSource, + int lineOffset) + { + Context cx = getCurrentContext(); + if (cx != null) { + cx.errorCount++; + return cx.getErrorReporter(). + runtimeError(message, sourceName, lineno, + lineSource, lineOffset); + } else { + throw new EvaluatorException(message); + } + } + + static EvaluatorException reportRuntimeError0(String messageId) { + return reportRuntimeError(getMessage0(messageId)); + } + + static EvaluatorException reportRuntimeError1 + (String messageId, Object arg1) + { + return reportRuntimeError(getMessage1(messageId, arg1)); + } + + static EvaluatorException reportRuntimeError2 + (String messageId, Object arg1, Object arg2) + { + return reportRuntimeError(getMessage2(messageId, arg1, arg2)); + } + + static EvaluatorException reportRuntimeError3 + (String messageId, Object arg1, Object arg2, Object arg3) + { + return reportRuntimeError(getMessage3(messageId, arg1, arg2, arg3)); + } + + /** + * Report a runtime error using the error reporter for the current thread. + * + * @param message the error message to report + * @see org.mozilla.javascript.ErrorReporter + */ + public static EvaluatorException reportRuntimeError(String message) { + int[] linep = { 0 }; + String filename = getSourcePositionFromStack(linep); + return Context.reportRuntimeError(message, filename, linep[0], null, 0); + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

+ * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @return the initialized scope + */ + public Scriptable initStandardObjects(ScriptableObject scope) { + return initStandardObjects(scope, false); + } + + /** + * Initialize the standard objects. + * + * Creates instances of the standard objects and their constructors + * (Object, String, Number, Date, etc.), setting up 'scope' to act + * as a global object as in ECMA 15.1.

+ * + * This method must be called to initialize a scope before scripts + * can be evaluated in that scope.

+ * + * This form of the method also allows for creating "sealed" standard + * objects. An object that is sealed cannot have properties added or + * removed. This is useful to create a "superglobal" that can be shared + * among several top-level objects. Note that sealing is not allowed in + * the current ECMA/ISO language specification, but is likely for + * the next version. + * + * @param scope the scope to initialize, or null, in which case a new + * object will be created to serve as the scope + * @param sealed whether or not to create sealed standard objects that + * cannot be modified. + * @return the initialized scope + * @since 1.4R3 + */ + public ScriptableObject initStandardObjects(ScriptableObject scope, + boolean sealed) + { + if (scope == null) + scope = new NativeObject(); + + BaseFunction.init(this, scope, sealed); + NativeObject.init(this, scope, sealed); + + Scriptable objectProto = ScriptableObject.getObjectPrototype(scope); + + // Function.prototype.__proto__ should be Object.prototype + Scriptable functionProto = ScriptableObject.getFunctionPrototype(scope); + functionProto.setPrototype(objectProto); + + // Set the prototype of the object passed in if need be + if (scope.getPrototype() == null) + scope.setPrototype(objectProto); + + // must precede NativeGlobal since it's needed therein + NativeError.init(this, scope, sealed); + NativeGlobal.init(this, scope, sealed); + + NativeArray.init(this, scope, sealed); + NativeString.init(this, scope, sealed); + NativeBoolean.init(this, scope, sealed); + NativeNumber.init(this, scope, sealed); + NativeDate.init(this, scope, sealed); + NativeMath.init(this, scope, sealed); + + NativeWith.init(this, scope, sealed); + NativeCall.init(this, scope, sealed); + NativeScript.init(this, scope, sealed); + + new LazilyLoadedCtor(scope, + "RegExp", + "org.mozilla.javascript.regexp.NativeRegExp", + sealed); + + // This creates the Packages and java package roots. + new LazilyLoadedCtor(scope, + "Packages", + "org.mozilla.javascript.NativeJavaPackage", + sealed); + new LazilyLoadedCtor(scope, + "java", + "org.mozilla.javascript.NativeJavaPackage", + sealed); + new LazilyLoadedCtor(scope, + "getClass", + "org.mozilla.javascript.NativeJavaPackage", + sealed); + + // Define the JavaAdapter class, allowing it to be overridden. + String adapterClass = "org.mozilla.javascript.JavaAdapter"; + String adapterProperty = "JavaAdapter"; + try { + adapterClass = System.getProperty(adapterClass, adapterClass); + adapterProperty = System.getProperty + ("org.mozilla.javascript.JavaAdapterClassName", + adapterProperty); + } + catch (SecurityException e) { + // We may not be allowed to get system properties. Just + // use the default adapter in that case. + } + + new LazilyLoadedCtor(scope, adapterProperty, adapterClass, sealed); + + return scope; + } + + /** + * Get the singleton object that represents the JavaScript Undefined value. + */ + public static Object getUndefinedValue() { + return Undefined.instance; + } + + /** + * Evaluate a JavaScript source string. + * + * The provided source name and line number are used for error messages + * and for producing debug information. + * + * @param scope the scope to execute in + * @param source the JavaScript source + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return the result of evaluating the string + * @exception JavaScriptException if an uncaught JavaScript exception + * occurred while evaluating the source string + * @see org.mozilla.javascript.SecuritySupport + */ + public Object evaluateString(Scriptable scope, String source, + String sourceName, int lineno, + Object securityDomain) + throws JavaScriptException + { + try { + Reader in = new StringReader(source); + return evaluateReader(scope, in, sourceName, lineno, + securityDomain); + } + catch (IOException ioe) { + // Should never occur because we just made the reader from a String + throw new RuntimeException(); + } + } + + /** + * Evaluate a reader as JavaScript source. + * + * All characters of the reader are consumed. + * + * @param scope the scope to execute in + * @param in the Reader to get JavaScript source from + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return the result of evaluating the source + * + * @exception IOException if an IOException was generated by the Reader + * @exception JavaScriptException if an uncaught JavaScript exception + * occurred while evaluating the Reader + */ + public Object evaluateReader(Scriptable scope, Reader in, + String sourceName, int lineno, + Object securityDomain) + throws IOException, JavaScriptException + { + Script script = compileReader(scope, in, sourceName, lineno, + securityDomain); + if (script != null) + return script.exec(this, scope); + else + return null; + } + + /** + * Check whether a string is ready to be compiled. + *

+ * stringIsCompilableUnit is intended to support interactive compilation of + * javascript. If compiling the string would result in an error + * that might be fixed by appending more source, this method + * returns false. In every other case, it returns true. + *

+ * Interactive shells may accumulate source lines, using this + * method after each new line is appended to check whether the + * statement being entered is complete. + * + * @param source the source buffer to check + * @return whether the source is ready for compilation + * @since 1.4 Release 2 + */ + synchronized public boolean stringIsCompilableUnit(String source) + { + Reader in = new StringReader(source); + // no source name or source text manager, because we're just + // going to throw away the result. + TokenStream ts = new TokenStream(in, null, null, 1); + + // Temporarily set error reporter to always be the exception-throwing + // DefaultErrorReporter. (This is why the method is synchronized...) + ErrorReporter currentReporter = + setErrorReporter(new DefaultErrorReporter()); + + boolean errorseen = false; + try { + IRFactory irf = new IRFactory(ts, null); + Parser p = new Parser(irf); + p.parse(ts); + } catch (IOException ioe) { + errorseen = true; + } catch (EvaluatorException ee) { + errorseen = true; + } finally { + // Restore the old error reporter. + setErrorReporter(currentReporter); + } + // Return false only if an error occurred as a result of reading past + // the end of the file, i.e. if the source could be fixed by + // appending more source. + if (errorseen && ts.eof()) + return false; + else + return true; + } + + /** + * Compiles the source in the given reader. + *

+ * Returns a script that may later be executed. + * Will consume all the source in the reader. + * + * @param scope if nonnull, will be the scope in which the script object + * is created. The script object will be a valid JavaScript object + * as if it were created using the JavaScript1.3 Script constructor + * @param in the input reader + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number for reporting errors + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return a script that may later be executed + * @see org.mozilla.javascript.Script#exec + * @exception IOException if an IOException was generated by the Reader + */ + public Script compileReader(Scriptable scope, Reader in, String sourceName, + int lineno, Object securityDomain) + throws IOException + { + return (Script) compile(scope, in, sourceName, lineno, securityDomain, + false); + } + + + /** + * Compile a JavaScript function. + *

+ * The function source must be a function definition as defined by + * ECMA (e.g., "function f(a) { return a; }"). + * + * @param scope the scope to compile relative to + * @param source the function definition source + * @param sourceName a string describing the source, such as a filename + * @param lineno the starting line number + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @return a Function that may later be called + * @see org.mozilla.javascript.Function + */ + public Function compileFunction(Scriptable scope, String source, + String sourceName, int lineno, + Object securityDomain) + { + Reader in = new StringReader(source); + try { + return (Function) compile(scope, in, sourceName, lineno, + securityDomain, true); + } + catch (IOException ioe) { + // Should never happen because we just made the reader + // from a String + throw new RuntimeException(); + } + } + + /** + * Decompile the script. + *

+ * The canonical source of the script is returned. + * + * @param script the script to decompile + * @param scope the scope under which to decompile + * @param indent the number of spaces to indent the result + * @return a string representing the script source + */ + public String decompileScript(Script script, Scriptable scope, + int indent) + { + NativeScript ns = (NativeScript) script; + ns.initScript(scope); + return ns.decompile(this, indent, false); + } + + /** + * Decompile a JavaScript Function. + *

+ * Decompiles a previously compiled JavaScript function object to + * canonical source. + *

+ * Returns function body of '[native code]' if no decompilation + * information is available. + * + * @param fun the JavaScript function to decompile + * @param indent the number of spaces to indent the result + * @return a string representing the function source + */ + public String decompileFunction(Function fun, int indent) { + if (fun instanceof BaseFunction) + return ((BaseFunction)fun).decompile(this, indent, false); + else + return "function " + fun.getClassName() + + "() {\n\t[native code]\n}\n"; + } + + /** + * Decompile the body of a JavaScript Function. + *

+ * Decompiles the body a previously compiled JavaScript Function + * object to canonical source, omitting the function header and + * trailing brace. + * + * Returns '[native code]' if no decompilation information is available. + * + * @param fun the JavaScript function to decompile + * @param indent the number of spaces to indent the result + * @return a string representing the function body source. + */ + public String decompileFunctionBody(Function fun, int indent) { + if (fun instanceof BaseFunction) + return ((BaseFunction)fun).decompile(this, indent, true); + else + // not sure what the right response here is. JSRef currently + // dumps core. + return "[native code]\n"; + } + + /** + * Create a new JavaScript object. + * + * Equivalent to evaluating "new Object()". + * @param scope the scope to search for the constructor and to evaluate + * against + * @return the new object + * @exception PropertyException if "Object" cannot be found in + * the scope + * @exception NotAFunctionException if the "Object" found in the scope + * is not a function + * @exception JavaScriptException if an uncaught JavaScript exception + * occurred while creating the object + */ + public Scriptable newObject(Scriptable scope) + throws PropertyException, + NotAFunctionException, + JavaScriptException + { + return newObject(scope, "Object", null); + } + + /** + * Create a new JavaScript object by executing the named constructor. + * + * The call newObject(scope, "Foo") is equivalent to + * evaluating "new Foo()". + * + * @param scope the scope to search for the constructor and to evaluate against + * @param constructorName the name of the constructor to call + * @return the new object + * @exception PropertyException if a property with the constructor + * name cannot be found in the scope + * @exception NotAFunctionException if the property found in the scope + * is not a function + * @exception JavaScriptException if an uncaught JavaScript exception + * occurred while creating the object + */ + public Scriptable newObject(Scriptable scope, String constructorName) + throws PropertyException, + NotAFunctionException, + JavaScriptException + { + return newObject(scope, constructorName, null); + } + + /** + * Creates a new JavaScript object by executing the named constructor. + * + * Searches scope for the named constructor, calls it with + * the given arguments, and returns the result.

+ * + * The code + *

+     * Object[] args = { "a", "b" };
+     * newObject(scope, "Foo", args)
+ * is equivalent to evaluating "new Foo('a', 'b')", assuming that the Foo + * constructor has been defined in scope. + * + * @param scope The scope to search for the constructor and to evaluate + * against + * @param constructorName the name of the constructor to call + * @param args the array of arguments for the constructor + * @return the new object + * @exception PropertyException if a property with the constructor + * name cannot be found in the scope + * @exception NotAFunctionException if the property found in the scope + * is not a function + * @exception JavaScriptException if an uncaught JavaScript exception + * occurs while creating the object + */ + public Scriptable newObject(Scriptable scope, String constructorName, + Object[] args) + throws PropertyException, + NotAFunctionException, + JavaScriptException + { + Object ctorVal = ScriptRuntime.getTopLevelProp(scope, constructorName); + if (ctorVal == Scriptable.NOT_FOUND) { + String message = getMessage1("msg.ctor.not.found", constructorName); + throw new PropertyException(message); + } + if (!(ctorVal instanceof Function)) { + String message = getMessage1("msg.not.ctor", constructorName); + throw new NotAFunctionException(message); + } + Function ctor = (Function) ctorVal; + return ctor.construct(this, ctor.getParentScope(), + (args == null) ? ScriptRuntime.emptyArgs : args); + } + + /** + * Create an array with a specified initial length. + *

+ * @param scope the scope to create the object in + * @param length the initial length (JavaScript arrays may have + * additional properties added dynamically). + * @return the new array object + */ + public Scriptable newArray(Scriptable scope, int length) { + Scriptable result = new NativeArray(length); + newArrayHelper(scope, result); + return result; + } + + /** + * Create an array with a set of initial elements. + *

+ * @param scope the scope to create the object in + * @param elements the initial elements. Each object in this array + * must be an acceptable JavaScript type. + * @return the new array object + */ + public Scriptable newArray(Scriptable scope, Object[] elements) { + Scriptable result = new NativeArray(elements); + newArrayHelper(scope, result); + return result; + } + + /** + * Get the elements of a JavaScript array. + *

+ * If the object defines a length property, a Java array with that + * length is created and initialized with the values obtained by + * calling get() on object for each value of i in [0,length-1]. If + * there is not a defined value for a property the Undefined value + * is used to initialize the corresponding element in the array. The + * Java array is then returned. + * If the object doesn't define a length property, null is returned. + * @param object the JavaScript array or array-like object + * @return a Java array of objects + * @since 1.4 release 2 + */ + public Object[] getElements(Scriptable object) { + double doubleLen = NativeArray.getLengthProperty(object); + if (doubleLen != doubleLen) + return null; + int len = (int) doubleLen; + Object[] result = new Object[len]; + for (int i=0; i < len; i++) { + Object elem = object.get(i, object); + result[i] = elem == Scriptable.NOT_FOUND ? Undefined.instance + : elem; + } + return result; + } + + /** + * Convert the value to a JavaScript boolean value. + *

+ * See ECMA 9.2. + * + * @param value a JavaScript value + * @return the corresponding boolean value converted using + * the ECMA rules + */ + public static boolean toBoolean(Object value) { + return ScriptRuntime.toBoolean(value); + } + + /** + * Convert the value to a JavaScript Number value. + *

+ * Returns a Java double for the JavaScript Number. + *

+ * See ECMA 9.3. + * + * @param value a JavaScript value + * @return the corresponding double value converted using + * the ECMA rules + */ + public static double toNumber(Object value) { + return ScriptRuntime.toNumber(value); + } + + /** + * Convert the value to a JavaScript String value. + *

+ * See ECMA 9.8. + *

+ * @param value a JavaScript value + * @return the corresponding String value converted using + * the ECMA rules + */ + public static String toString(Object value) { + return ScriptRuntime.toString(value); + } + + /** + * Convert the value to an JavaScript object value. + *

+ * Note that a scope must be provided to look up the constructors + * for Number, Boolean, and String. + *

+ * See ECMA 9.9. + *

+ * Additionally, arbitrary Java objects and classes will be + * wrapped in a Scriptable object with its Java fields and methods + * reflected as JavaScript properties of the object. + * + * @param value any Java object + * @param scope global scope containing constructors for Number, + * Boolean, and String + * @return new JavaScript object + */ + public static Scriptable toObject(Object value, Scriptable scope) { + return ScriptRuntime.toObject(scope, value, null); + } + + /** + * Convert the value to an JavaScript object value. + *

+ * Note that a scope must be provided to look up the constructors + * for Number, Boolean, and String. + *

+ * See ECMA 9.9. + *

+ * Additionally, arbitrary Java objects and classes will be + * wrapped in a Scriptable object with its Java fields and methods + * reflected as JavaScript properties of the object. If the + * "staticType" parameter is provided, it will be used as the static + * type of the Java value to create. + * + * @param value any Java object + * @param scope global scope containing constructors for Number, + * Boolean, and String + * @param staticType the static type of the Java value to create + * @return new JavaScript object + */ + public static Scriptable toObject(Object value, Scriptable scope, + Class staticType) { + if (value == null && staticType != null) + return null; + return ScriptRuntime.toObject(scope, value, staticType); + } + + /** + * Tell whether debug information is being generated. + * @since 1.3 + */ + public boolean isGeneratingDebug() { + return generatingDebug; + } + + /** + * Specify whether or not debug information should be generated. + *

+ * Setting the generation of debug information on will set the + * optimization level to zero. + * @since 1.3 + */ + public void setGeneratingDebug(boolean generatingDebug) { + generatingDebugChanged = true; + if (generatingDebug) + setOptimizationLevel(0); + this.generatingDebug = generatingDebug; + } + + /** + * Tell whether source information is being generated. + * @since 1.3 + */ + public boolean isGeneratingSource() { + return generatingSource; + } + + /** + * Specify whether or not source information should be generated. + *

+ * Without source information, evaluating the "toString" method + * on JavaScript functions produces only "[native code]" for + * the body of the function. + * Note that code generated without source is not fully ECMA + * conformant. + * @since 1.3 + */ + public void setGeneratingSource(boolean generatingSource) { + this.generatingSource = generatingSource; + } + + /** + * Get the current optimization level. + *

+ * The optimization level is expressed as an integer between -1 and + * 9. + * @since 1.3 + * + */ + public int getOptimizationLevel() { + return optimizationLevel; + } + + /** + * Set the current optimization level. + *

+ * The optimization level is expected to be an integer between -1 and + * 9. Any negative values will be interpreted as -1, and any values + * greater than 9 will be interpreted as 9. + * An optimization level of -1 indicates that interpretive mode will + * always be used. Levels 0 through 9 indicate that class files may + * be generated. Higher optimization levels trade off compile time + * performance for runtime performance. + * The optimizer level can't be set greater than -1 if the optimizer + * package doesn't exist at run time. + * @param optimizationLevel an integer indicating the level of + * optimization to perform + * @since 1.3 + * + */ + public void setOptimizationLevel(int optimizationLevel) { + if (optimizationLevel < 0) { + optimizationLevel = -1; + } else if (optimizationLevel > 9) { + optimizationLevel = 9; + } + if (codegenClass == null) + optimizationLevel = -1; + this.optimizationLevel = optimizationLevel; + } + + /** + * Get the current target class file name. + *

+ * If nonnull, requests to compile source will result in one or + * more class files being generated. + * @since 1.3 + */ + public String getTargetClassFileName() { + return nameHelper == null + ? null + : nameHelper.getTargetClassFileName(); + } + + /** + * Set the current target class file name. + *

+ * If nonnull, requests to compile source will result in one or + * more class files being generated. If null, classes will only + * be generated in memory. + * + * @since 1.3 + */ + public void setTargetClassFileName(String classFileName) { + if (nameHelper != null) + nameHelper.setTargetClassFileName(classFileName); + } + + /** + * Get the current package to generate classes into. + * + * @since 1.3 + */ + public String getTargetPackage() { + return (nameHelper == null) ? null : nameHelper.getTargetPackage(); + } + + /** + * Set the package to generate classes into. + * + * @since 1.3 + */ + public void setTargetPackage(String targetPackage) { + if (nameHelper != null) + nameHelper.setTargetPackage(targetPackage); + } + + /** + * Get the current interface to write class bytes into. + * + * @see ClassOutput + * @since 1.5 Release 2 + */ + public ClassOutput getClassOutput() { + return nameHelper == null ? null : nameHelper.getClassOutput(); + } + + /** + * Set the interface to write class bytes into. + * Unless setTargetClassFileName() has been called classOutput will be + * used each time the javascript compiler has generated the bytecode for a + * script class. + * + * @see ClassOutput + * @since 1.5 Release 2 + */ + public void setClassOutput(ClassOutput classOutput) { + if (nameHelper != null) + nameHelper.setClassOutput(classOutput); + } + + /** + * Add a Context listener. + */ + public static void addContextListener(ContextListener listener) { + synchronized (staticDataLock) { + contextListeners = ListenerArray.add(contextListeners, listener); + } + } + + /** + * Remove a Context listener. + * @param listener the listener to remove. + */ + public static void removeContextListener(ContextListener listener) { + synchronized (staticDataLock) { + contextListeners = ListenerArray.remove(contextListeners, listener); + } + } + + /** + * Set the security support for this context. + *

SecuritySupport may only be set if it is currently null. + * Otherwise a SecurityException is thrown. + * @param supportObj a SecuritySupport object + * @throws SecurityException if there is already a SecuritySupport + * object for this Context + */ + public synchronized void setSecuritySupport(SecuritySupport supportObj) { + if (securitySupport != null) { + throw new SecurityException("Cannot overwrite existing " + + "SecuritySupport object"); + } + securitySupport = supportObj; + } + + /** + * Return true if a security domain is required on calls to + * compile and evaluate scripts. + * + * @since 1.4 Release 2 + */ + public static boolean isSecurityDomainRequired() { + return requireSecurityDomain; + } + + /** + * Returns the security context associated with the innermost + * script or function being executed by the interpreter. + * @since 1.4 release 2 + */ + public Object getInterpreterSecurityDomain() { + return interpreterSecurityDomain; + } + + /** + * Returns true if the class parameter is a class in the + * interpreter. Typically used by embeddings that get a class + * context to check security. These embeddings must know + * whether to get the security context associated with the + * interpreter or not. + * + * @param cl a class to test whether or not it is an interpreter + * class + * @return true if cl is an interpreter class + * @since 1.4 release 2 + */ + public boolean isInterpreterClass(Class cl) { + return cl == Interpreter.class; + } + + /** + * Set the class that the generated target will extend. + * + * @param extendsClass the class it extends + */ + public void setTargetExtends(Class extendsClass) { + if (nameHelper != null) { + nameHelper.setTargetExtends(extendsClass); + } + } + + /** + * Set the interfaces that the generated target will implement. + * + * @param implementsClasses an array of Class objects, one for each + * interface the target will extend + */ + public void setTargetImplements(Class[] implementsClasses) { + if (nameHelper != null) { + nameHelper.setTargetImplements(implementsClasses); + } + } + + /** + * Get a value corresponding to a key. + *

+ * Since the Context is associated with a thread it can be + * used to maintain values that can be later retrieved using + * the current thread. + *

+ * Note that the values are maintained with the Context, so + * if the Context is disassociated from the thread the values + * cannot be retreived. Also, if private data is to be maintained + * in this manner the key should be a java.lang.Object + * whose reference is not divulged to untrusted code. + * @param key the key used to lookup the value + * @return a value previously stored using putThreadLocal. + */ + public Object getThreadLocal(Object key) { + if (hashtable == null) + return null; + return hashtable.get(key); + } + + /** + * Put a value that can later be retrieved using a given key. + *

+ * @param key the key used to index the value + * @param value the value to save + */ + public void putThreadLocal(Object key, Object value) { + if (hashtable == null) + hashtable = new Hashtable(); + hashtable.put(key, value); + } + + /** + * Remove values from thread-local storage. + * @param key the key for the entry to remove. + * @since 1.5 release 2 + */ + public void removeThreadLocal(Object key) { + if (hashtable == null) + return; + hashtable.remove(key); + } + + /** + * Return whether functions are compiled by this context using + * dynamic scope. + *

+ * If functions are compiled with dynamic scope, then they execute + * in the scope of their caller, rather than in their parent scope. + * This is useful for sharing functions across multiple scopes. + * @since 1.5 Release 1 + */ + public boolean hasCompileFunctionsWithDynamicScope() { + return compileFunctionsWithDynamicScopeFlag; + } + + /** + * Set whether functions compiled by this context should use + * dynamic scope. + *

+ * @param flag if true, compile functions with dynamic scope + * @since 1.5 Release 1 + */ + public void setCompileFunctionsWithDynamicScope(boolean flag) { + compileFunctionsWithDynamicScopeFlag = flag; + } + + /** + * Set whether to cache some values statically. + *

+ * By default, the engine will cache some values statically + * (reflected Java classes, for instance). This can speed + * execution dramatically, but increases the memory footprint. + * Also, with caching enabled, references may be held to + * objects past the lifetime of any real usage. + *

+ * If caching is enabled and this method is called with a + * false argument, the caches will be emptied. + * So one strategy could be to clear the caches at times + * appropriate to the application. + *

+ * Caching is enabled by default. + * + * @param cachingEnabled if true, caching is enabled + * @since 1.5 Release 1 + */ + public static void setCachingEnabled(boolean cachingEnabled) { + if (isCachingEnabled && !cachingEnabled) { + // Caching is being turned off. Empty caches. + JavaMembers.classTable = new Hashtable(); + nameHelper.reset(); + } + isCachingEnabled = cachingEnabled; + FunctionObject.setCachingEnabled(cachingEnabled); + } + + /** + * Set a WrapHandler for this Context. + *

+ * The WrapHandler allows custom object wrapping behavior for + * Java object manipulated with JavaScript. + * @see org.mozilla.javascript.WrapHandler + * @since 1.5 Release 2 + */ + public void setWrapHandler(WrapHandler wrapHandler) { + this.wrapHandler = wrapHandler; + } + + /** + * Return the current WrapHandler, or null if none is defined. + * @see org.mozilla.javascript.WrapHandler + * @since 1.5 Release 2 + */ + public WrapHandler getWrapHandler() { + return wrapHandler; + } + + public DebuggableEngine getDebuggableEngine() { + if (debuggableEngine == null) + debuggableEngine = new DebuggableEngineImpl(this); + return debuggableEngine; + } + + + /** + * if hasFeature(FEATURE_NON_ECMA_GET_YEAR) returns true, + * Date.prototype.getYear subtructs 1900 only if 1900 <= date < 2000 + * in deviation with Ecma B.2.4 + */ + public static final int FEATURE_NON_ECMA_GET_YEAR = 1; + + /** + * Controls certain aspects of script semantics. + * Should be overwritten to alter default behavior. + * @param featureIndex feature index to check + * @return true if the featureIndex feature is turned on + * @see #FEATURE_NON_ECMA_GET_YEAR + */ + public boolean hasFeature(int featureIndex) { + if (featureIndex == FEATURE_NON_ECMA_GET_YEAR) { + /* + * During the great date rewrite of 1.3, we tried to track the + * evolving ECMA standard, which then had a definition of + * getYear which always subtracted 1900. Which we + * implemented, not realizing that it was incompatible with + * the old behavior... now, rather than thrash the behavior + * yet again, we've decided to leave it with the - 1900 + * behavior and point people to the getFullYear method. But + * we try to protect existing scripts that have specified a + * version... + */ + return (version == Context.VERSION_1_0 + || version == Context.VERSION_1_1 + || version == Context.VERSION_1_2); + } + throw new RuntimeException("Bad feature index: " + featureIndex); + } + + /** + * Get/Set threshold of executed instructions counter that triggers call to + * observeInstructionCount(). + * When the threshold is zero, instruction counting is disabled, + * otherwise each time the run-time executes at least the threshold value + * of script instructions, observeInstructionCount() will + * be called. + */ + public int getInstructionObserverThreshold() { + return instructionThreshold; + } + + public void setInstructionObserverThreshold(int threshold) { + instructionThreshold = threshold; + } + + /** + * Allow application to monitor counter of executed script instructions + * in Context subclasses. + * Run-time calls this when instruction counting is enabled and the counter + * reaches limit set by setInstructionObserverThreshold(). + * The method is useful to observe long running scripts and if necessary + * to terminate them. + * @param instructionCount amount of script instruction executed since + * last call to observeInstructionCount + * @throws Error to terminate the script + */ + protected void observeInstructionCount(int instructionCount) {} + + /********** end of API **********/ + + void pushFrame(DebugFrame frame) { + if (frameStack == null) + frameStack = new java.util.Stack(); + frameStack.push(frame); + } + + void popFrame() { + frameStack.pop(); + } + + + + static String getMessage0(String messageId) { + return getMessage(messageId, null); + } + + static String getMessage1(String messageId, Object arg1) { + Object[] arguments = {arg1}; + return getMessage(messageId, arguments); + } + + static String getMessage2(String messageId, Object arg1, Object arg2) { + Object[] arguments = {arg1, arg2}; + return getMessage(messageId, arguments); + } + + static String getMessage3 + (String messageId, Object arg1, Object arg2, Object arg3) { + Object[] arguments = {arg1, arg2, arg3}; + return getMessage(messageId, arguments); + } + /** + * Internal method that reports an error for missing calls to + * enter(). + */ + static Context getContext() { + Thread t = Thread.currentThread(); + Context cx = (Context) threadContexts.get(t); + if (cx == null) { + throw new RuntimeException( + "No Context associated with current Thread"); + } + return cx; + } + + /** GCJ doesn't have an easy way to bundle up .properties files, so we do this */ + static class HardCodedResourceBundle extends ListResourceBundle { + public Object[][] getContents() { return contents; } + static final Object[][] contents = { + { "msg.dup.parms", "Duplicate parameter name \"{0}\"." }, + { "msg.ctor.not.found", "Constructor for \"{0}\" not found." }, + { "msg.not.ctor", "\"{0}\" is not a constructor." }, + { "msg.varargs.ctor", "Method or constructor \"{0}\" must be static with the signature \"(Context cx, Object[] args, Function ctorObj, boolean inNewExpr)\" to define a variable arguments constructor." }, + { "msg.varargs.fun", "Method \"{0}\" must be static with the signature \"(Context cx, Scriptable thisObj, Object[] args, Function funObj)\" to define a variable arguments function." }, + { "msg.incompat.call", "Method \"{0}\" called on incompatible object." }, + { "msg.bad.parms", "Bad method parameters for \"{0}\"." }, + { "msg.no.overload", "Method \"{0}\" occurs multiple times in class \"{1}\"." }, + { "msg.method.not.found", "Method \"{0}\" not found in \"{1}\"." }, + { "msg.bad.for.in.lhs", "Invalid left-hand side of for..in loop." }, + { "msg.bad.lhs.assign", "Invalid assignment left-hand side." }, + { "msg.mult.index", "Only one variable allowed in for..in loop." }, + { "msg.cant.convert", "Can''t convert to type \"{0}\"." }, + { "msg.cant.call.indirect", "Function \"{0}\" must be called directly, and not by way of a function of another name." }, + { "msg.eval.nonstring", "Calling eval() with anything other than a primitive string value will simply return the value. Is this what you intended?" }, + { "msg.only.from.new", "\"{0}\" may only be invoked from a \"new\" expression." }, + { "msg.deprec.ctor", "The \"{0}\" constructor is deprecated." }, + { "msg.no.function.ref.found.in", "no source found in {1} to decompile function reference {0}" }, + { "msg.no.function.ref.found", "no source found to decompile function reference {0}" }, + { "msg.arg.isnt.array", "second argument to Function.prototype.apply must be an array" }, + { "msg.bad.esc.mask", "invalid string escape mask" }, + { "msg.cant.instantiate", "error instantiating ({0}): class {1} is interface or abstract" }, + { "msg.bad.ctor.sig", "Found constructor with wrong signature: {0} calling {1} with signature {2}" }, + { "msg.not.java.obj", "Expected argument to getClass() to be a Java object." }, + { "msg.no.java.ctor", "Java constructor for \"{0}\" with arguments \"{1}\" not found." }, + { "msg.method.ambiguous", "The choice of Java method {0}.{1} matching JavaScript argument types ({2}) is ambiguous; candidate methods are: {3}" }, + { "msg.constructor.ambiguous", "The choice of Java constructor {0} matching JavaScript argument types ({1}) is ambiguous; candidate constructors are: {2}" }, + { "msg.conversion.not.allowed", "Cannot convert {0} to {1}" }, + { "msg.bad.quant", "Invalid quantifier {0}" }, + { "msg.overlarge.max", "Overly large maximum {0}" }, + { "msg.zero.quant", "Zero quantifier {0}" }, + { "msg.max.lt.min", "Maximum {0} less than minimum" }, + { "msg.unterm.quant", "Unterminated quantifier {0}" }, + { "msg.unterm.paren", "Unterminated parenthetical {0}" }, + { "msg.unterm.class", "Unterminated character class {0}" }, + { "msg.bad.range", "Invalid range in character class." }, + { "msg.trail.backslash", "Trailing \\ in regular expression." }, + { "msg.no.regexp", "Regular expressions are not available." }, + { "msg.bad.backref", "back-reference exceeds number of capturing parentheses." }, + { "msg.dup.label", "Duplicate label {0}." }, + { "msg.undef.label", "Undefined label {0}." }, + { "msg.bad.break", "Unlabelled break must be inside loop or switch." }, + { "msg.continue.outside", "continue must be inside loop." }, + { "msg.continue.nonloop", "Can only continue to labeled iteration statement." }, + { "msg.fn.redecl", "Function \"{0}\" redeclared; prior definition will be ignored." }, + { "msg.no.paren.parms", "missing ( before function parameters" }, + { "msg.no.parm", "missing formal parameter" }, + { "msg.no.paren.after.parms", "missing ) after formal parameters" }, + { "msg.no.brace.body", "missing '{' before function body" }, + { "msg.no.brace.after.body", "missing } after function body" }, + { "msg.no.paren.cond", "missing ( before condition" }, + { "msg.no.paren.after.cond", "missing ) after condition" }, + { "msg.no.semi.stmt", "missing ; before statement" }, + { "msg.no.name.after.dot", "missing name after . operator" }, + { "msg.no.bracket.index", "missing ] in index expression" }, + { "msg.no.paren.switch", "missing ( before switch expression" }, + { "msg.no.paren.after.switch", "missing ) after switch expression" }, + { "msg.no.brace.switch", "missing '{' before switch body" }, + { "msg.bad.switch", "invalid switch statement" }, + { "msg.no.colon.case", "missing : after case expression" }, + { "msg.no.while.do", "missing while after do-loop body" }, + { "msg.no.paren.for", "missing ( after for" }, + { "msg.no.semi.for", "missing ; after for-loop initializer" }, + { "msg.no.semi.for.cond", "missing ; after for-loop condition" }, + { "msg.no.paren.for.ctrl", "missing ) after for-loop control" }, + { "msg.no.paren.with", "missing ( before with-statement object" }, + { "msg.no.paren.after.with", "missing ) after with-statement object" }, + { "msg.bad.return", "invalid return" }, + { "msg.no.brace.block", "missing } in compound statement" }, + { "msg.bad.label", "invalid label" }, + { "msg.bad.var", "missing variable name" }, + { "msg.bad.var.init", "invalid variable initialization" }, + { "msg.no.colon.cond", "missing : in conditional expression" }, + { "msg.no.paren.arg", "missing ) after argument list" }, + { "msg.no.bracket.arg", "missing ] after element list" }, + { "msg.bad.prop", "invalid property id" }, + { "msg.no.colon.prop", "missing : after property id" }, + { "msg.no.brace.prop", "missing } after property list" }, + { "msg.no.paren", "missing ) in parenthetical" }, + { "msg.reserved.id", "identifier is a reserved word" }, + { "msg.no.paren.catch", "missing ( before catch-block condition" }, + { "msg.bad.catchcond", "invalid catch block condition" }, + { "msg.catch.unreachable", "any catch clauses following an unqualified catch are unreachable" }, + { "msg.no.brace.catchblock", "missing '{' before catch-block body" }, + { "msg.try.no.catchfinally", "''try'' without ''catch'' or ''finally''" }, + { "msg.syntax", "syntax error" }, + { "msg.assn.create", "Assignment to undefined \"{0}\" will create a new variable. Add a variable statement at the top level scope to remove this warning." }, + { "msg.prop.not.found", "Property not found." }, + { "msg.invalid.type", "Invalid JavaScript value of type {0}" }, + { "msg.primitive.expected", "Primitive type expected (had {0} instead)" }, + { "msg.null.to.object", "Cannot convert null to an object." }, + { "msg.undef.to.object", "Cannot convert undefined to an object." }, + { "msg.cyclic.value", "Cyclic {0} value not allowed." }, + { "msg.is.not.defined", "\"{0}\" is not defined." }, + { "msg.isnt.function", "{0} is not a function." }, + { "msg.bad.default.value", "Object''s getDefaultValue() method returned an object." }, + { "msg.instanceof.not.object", " Can''t use instanceof on a non-object." }, + { "msg.instanceof.bad.prototype", " ''prototype'' property of {0} is not an object." }, + { "msg.bad.radix", " illegal radix {0}." }, + { "msg.default.value", "Cannot find default value for object." }, + { "msg.zero.arg.ctor", "Cannot load class \"{0}\" which has no zero-parameter constructor." }, + { "msg.multiple.ctors", "Cannot have more than one constructor method, but found both {0} and {1}." }, + { "msg.ctor.multiple.parms", "Can''t define constructor or class {0} since more than one constructor has multiple parameters." }, + { "msg.extend.scriptable", "{0} must extend ScriptableObject in order to define property {1}." }, + { "msg.bad.getter.parms", "In order to define a property, getter {0} must have zero parameters or a single ScriptableObject parameter." }, + { "msg.obj.getter.parms", "Expected static or delegated getter {0} to take a ScriptableObject parameter." }, + { "msg.getter.static", "Getter and setter must both be static or neither be static." }, + { "msg.setter2.parms", "Two-parameter setter must take a ScriptableObject as its first parameter." }, + { "msg.setter1.parms", "Expected single parameter setter for {0}" }, + { "msg.setter2.expected", "Expected static or delegated setter {0} to take two parameters." }, + { "msg.setter.parms", "Expected either one or two parameters for setter." }, + { "msg.add.sealed", "Cannot add a property to a sealed object." }, + { "msg.remove.sealed", "Cannot remove a property from a sealed object." }, + { "msg.token.replaces.pushback", "ungot token {0} replaces pushback token {1}" }, + { "msg.missing.exponent", "missing exponent" }, + { "msg.caught.nfe", "number format error: {0}" }, + { "msg.unterminated.string.lit", "unterminated string literal" }, + { "msg.oct.esc.too.large", "octal escape too large" }, + { "msg.nested.comment", "nested comment" }, + { "msg.unterminated.comment", "unterminated comment" }, + { "msg.unterminated.re.lit", "unterminated regular expression literal" }, + { "msg.invalid.re.flag", "invalid flag after regular expression" }, + { "msg.no.re.input.for", "no input for {0}" }, + { "msg.illegal.character", "illegal character" }, + { "msg.invalid.escape", "invalid Unicode escape sequence" }, + { "msg.bad.octal.literal", "illegal octal literal digit {0}; interpreting it as a decimal digit" }, + { "msg.undefined", "The undefined value has no properties." }, + { "msg.java.internal.field.type", "Internal error: type conversion of {0} to assign to {1} on {2} failed." }, + { "msg.java.conversion.implicit_method", "Can''t find converter method \"{0}\" on class {1}." }, + { "msg.java.method.assign", "Java method \"{0}\" cannot be assigned to." }, + { "msg.java.internal.private", "Internal error: attempt to access private/protected field \"{0}\"." }, + { "msg.java.no_such_method", "Can''t find method {0}." }, + { "msg.script.is.not.constructor", "Script objects are not constructors." }, + { "msg.nonjava.method", "Java method \"{0}\" was invoked with a ''this'' value that was not a Java object." }, + { "msg.java.member.not.found", "Java class \"{0}\" has no public instance field or method named \"{1}\"." }, + { "msg.pkg.int", "Java package names may not be numbers." }, + { "msg.ambig.import", "Ambiguous import: \"{0}\" and and \"{1}\"." }, + { "msg.not.pkg", "Function importPackage must be called with a package; had \"{0}\" instead." }, + { "msg.not.class", "Function importClass must be called with a class; had \"{0}\" instead." }, + { "msg.prop.defined", "Cannot import \"{0}\" since a property by that name is already defined." }, + { "msg.arraylength.bad", "Inappropriate array length." }, + { "msg.bad.uri", "Malformed URI sequence." }, + { "msg.bad.precision", "Precision {0} out of range." } + }; + } + + static final ResourceBundle myresources = new HardCodedResourceBundle(); + + /* OPT there's a noticable delay for the first error! Maybe it'd + * make sense to use a ListResourceBundle instead of a properties + * file to avoid (synchronized) text parsing. + */ + static String getMessage(String messageId, Object[] arguments) { + Context cx = getCurrentContext(); + Locale locale = cx != null ? cx.getLocale() : Locale.getDefault(); + + // ResourceBundle does cacheing. + ResourceBundle rb = myresources; + + String formatString; + try { + formatString = rb.getString(messageId); + } catch (java.util.MissingResourceException mre) { + throw new RuntimeException + ("no message resource found for message property "+ messageId); + } + + /* + * It's OK to format the string, even if 'arguments' is null; + * we need to format it anyway, to make double ''s collapse to + * single 's. + */ + // TODO: MessageFormat is not available on pJava + MessageFormat formatter = new MessageFormat(formatString); + return formatter.format(arguments); + } + + // debug flags + static final boolean printTrees = false; + + /** + * Compile a script. + * + * Reads script source from the reader and compiles it, returning + * a class for either the script or the function depending on the + * value of returnFunction. + * + * @param scope the scope to compile relative to + * @param in the Reader to read source from + * @param sourceName the name of the origin of the source (usually + * a file or URL) + * @param lineno the line number of the start of the source + * @param securityDomain an arbitrary object that specifies security + * information about the origin or owner of the script. For + * implementations that don't care about security, this value + * may be null. + * @param returnFunction if true, will expect the source to contain + * a function; return value is assumed to + * then be a org.mozilla.javascript.Function + * @return a class for the script or function + * @see org.mozilla.javascript.Context#compileReader + */ + private Object compile(Scriptable scope, Reader in, String sourceName, + int lineno, Object securityDomain, + boolean returnFunction) + throws IOException + { + if (debugger != null && in != null) { + in = new DebugReader(in); + } + TokenStream ts = new TokenStream(in, scope, sourceName, lineno); + return compile(scope, ts, securityDomain, in, returnFunction); + } + + private static Class codegenClass = null; + private static ClassNameHelper nameHelper = null; + static { + /* + try { + codegenClass = Class.forName( + "org.mozilla.javascript.optimizer.Codegen"); + Class nameHelperClass = Class.forName( + "org.mozilla.javascript.optimizer.OptClassNameHelper"); + nameHelper = (ClassNameHelper)nameHelperClass.newInstance(); + } catch (ClassNotFoundException x) { + // ...must be running lite, that's ok + codegenClass = null; + } catch (IllegalAccessException x) { + codegenClass = null; + } catch (InstantiationException x) { + codegenClass = null; + } + */ + } + + private Interpreter getCompiler() { + if (codegenClass != null) { + try { + return (Interpreter) codegenClass.newInstance(); + } + catch (SecurityException x) { + } + catch (IllegalArgumentException x) { + } + catch (InstantiationException x) { + } + catch (IllegalAccessException x) { + } + // fall through + } + return new Interpreter(); + } + + private Object compile(Scriptable scope, TokenStream ts, + Object securityDomain, Reader in, + boolean returnFunction) + throws IOException + { + Interpreter compiler = optimizationLevel == -1 + ? new Interpreter() + : getCompiler(); + + errorCount = 0; + IRFactory irf = compiler.createIRFactory(ts, nameHelper, scope); + Parser p = new Parser(irf); + Node tree = (Node) p.parse(ts); + if (tree == null) + return null; + + tree = compiler.transform(tree, ts, scope); + + if (printTrees) + System.out.println(tree.toStringTree()); + + if (returnFunction) { + Node first = tree.getFirstChild(); + if (first == null) + return null; + tree = (Node) first.getProp(Node.FUNCTION_PROP); + if (tree == null) + return null; + } + + if (in instanceof DebugReader) { + DebugReader dr = (DebugReader) in; + tree.putProp(Node.DEBUGSOURCE_PROP, dr.getSaved()); + } + + Object result = compiler.compile(this, scope, tree, securityDomain, + securitySupport, nameHelper); + + return errorCount == 0 ? result : null; + } + + static String getSourcePositionFromStack(int[] linep) { + Context cx = getCurrentContext(); + if (cx == null) + return null; + if (cx.interpreterLine > 0 && cx.interpreterSourceFile != null) { + linep[0] = cx.interpreterLine; + return cx.interpreterSourceFile; + } + /** + * A bit of a hack, but the only way to get filename and line + * number from an enclosing frame. + */ + CharArrayWriter writer = new CharArrayWriter(); + RuntimeException re = new RuntimeException(); + re.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + int open = -1; + int close = -1; + int colon = -1; + for (int i=0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == ':') + colon = i; + else if (c == '(') + open = i; + else if (c == ')') + close = i; + else if (c == '\n' && open != -1 && close != -1 && colon != -1 && + open < colon && colon < close) + { + String fileStr = s.substring(open + 1, colon); + if (fileStr.endsWith(".js")) { + String lineStr = s.substring(colon + 1, close); + try { + linep[0] = Integer.parseInt(lineStr); + return fileStr; + } + catch (NumberFormatException e) { + // fall through + } + } + open = close = colon = -1; + } + } + + return null; + } + + RegExpProxy getRegExpProxy() { + if (regExpProxy == null) { + try { + Class c = Class.forName( + "org.mozilla.javascript.regexp.RegExpImpl"); + regExpProxy = (RegExpProxy) c.newInstance(); + return regExpProxy; + } catch (ClassNotFoundException e) { + } catch (InstantiationException e) { + } catch (IllegalAccessException e) { + } + } + return regExpProxy; + } + + private void newArrayHelper(Scriptable scope, Scriptable array) { + array.setParentScope(scope); + Object ctor = ScriptRuntime.getTopLevelProp(scope, "Array"); + if (ctor != null && ctor instanceof Scriptable) { + Scriptable s = (Scriptable) ctor; + array.setPrototype((Scriptable) s.get("prototype", s)); + } + } + + final boolean isVersionECMA1() { + return version == VERSION_DEFAULT || version >= VERSION_1_3; + } + + /** + * Get the security context from the given class. + *

+ * When some form of security check needs to be done, the class context + * must retrieved from the security manager to determine what class is + * requesting some form of privileged access. + * @since 1.4 release 2 + */ + Object getSecurityDomainFromClass(Class cl) { + if (cl == Interpreter.class) + return interpreterSecurityDomain; + return securitySupport.getSecurityDomain(cl); + } + + SecuritySupport getSecuritySupport() { + return securitySupport; + } + + Object getSecurityDomainForStackDepth(int depth) { + Object result = null; + if (securitySupport != null) { + Class[] classes = securitySupport.getClassContext(); + if (classes != null) { + if (depth != -1) { + int depth1 = depth + 1; + result = getSecurityDomainFromClass(classes[depth1]); + } else { + for (int i=1; i < classes.length; i++) { + result = getSecurityDomainFromClass(classes[i]); + if (result != null) + break; + } + } + } + } + if (result != null) + return result; + if (requireSecurityDomain) + checkSecurityDomainRequired(); + return null; + } + + private static boolean requireSecurityDomain = false; + private static boolean resourceMissing = false; + + final static String securityResourceName = "org.mozilla.javascript.resources.Security"; + + final public static void checkSecurityDomainRequired() { } + + public boolean isGeneratingDebugChanged() { + return generatingDebugChanged; + } + + + /** + * Add a name to the list of names forcing the creation of real + * activation objects for functions. + * + * @param name the name of the object to add to the list + */ + public void addActivationName(String name) { + if (activationNames == null) + activationNames = new Hashtable(5); + activationNames.put(name, name); + } + + /** + * Check whether the name is in the list of names of objects + * forcing the creation of activation objects. + * + * @param name the name of the object to test + * + * @return true if an function activation object is needed. + */ + public boolean isActivationNeeded(String name) { + if ("arguments".equals(name)) + return true; + return activationNames != null && activationNames.containsKey(name); + } + + /** + * Remove a name from the list of names forcing the creation of real + * activation objects for functions. + * + * @param name the name of the object to remove from the list + */ + public void removeActivationName(String name) { + if (activationNames != null) + activationNames.remove(name); + } + + + static final boolean useJSObject = false; + + /** + * The activation of the currently executing function or script. + */ + NativeCall currentActivation; + + // for Objects, Arrays to tag themselves as being printed out, + // so they don't print themselves out recursively. + Hashtable iterating; + + Object interpreterSecurityDomain; + + int version; + int errorCount; + static boolean isCachingEnabled = true; + + private SecuritySupport securitySupport; + private ErrorReporter errorReporter; + private Thread currentThread; + private static Hashtable threadContexts = new Hashtable(11); + private RegExpProxy regExpProxy; + private Locale locale; + private boolean generatingDebug; + private boolean generatingDebugChanged; + private boolean generatingSource=true; + private boolean compileFunctionsWithDynamicScopeFlag; + private int optimizationLevel; + WrapHandler wrapHandler; + Debugger debugger; + DebuggableEngine debuggableEngine; + boolean inLineStepMode; + java.util.Stack frameStack; + private int enterCount; + private Object[] listeners; + private Hashtable hashtable; + + /** + * This is the list of names of objects forcing the creation of + * function activation records. + */ + private Hashtable activationNames; + + // Private lock for static fields to avoid a possibility of denial + // of service via synchronized (Context.class) { while (true) {} } + private static final Object staticDataLock = new Object(); + private static Object[] contextListeners; + public Function currentFunction; + Vector arrayCache = new Vector(10); + + // For the interpreter to indicate line/source for error reports. + public int interpreterLine; + public String interpreterSourceFile; + + public int stackDepth = 0; + + // For instruction counting (interpreter only) + int instructionCount; + int instructionThreshold; +} diff --git a/src/org/mozilla/javascript/ContextListener.java b/src/org/mozilla/javascript/ContextListener.java new file mode 100644 index 0000000..fb8c040 --- /dev/null +++ b/src/org/mozilla/javascript/ContextListener.java @@ -0,0 +1,53 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +// API class + +package org.mozilla.javascript; + +/** + * Embeddings that wish to + * @see org.mozilla.javascript.Context#addContextListener + */ +public interface ContextListener { + + public void contextCreated(Context cx); + + public void contextEntered(Context cx); + + public void contextExited(Context cx); + + public void contextReleased(Context cx); +} diff --git a/src/org/mozilla/javascript/DToA.java b/src/org/mozilla/javascript/DToA.java new file mode 100644 index 0000000..76cde25 --- /dev/null +++ b/src/org/mozilla/javascript/DToA.java @@ -0,0 +1,1197 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Waldemar Horwat + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +import java.math.BigInteger; + +class DToA { + + +/* "-0.0000...(1073 zeros after decimal point)...0001\0" is the longest string that we could produce, + * which occurs when printing -5e-324 in binary. We could compute a better estimate of the size of + * the output string and malloc fewer bytes depending on d and base, but why bother? */ + + static final int DTOBASESTR_BUFFER_SIZE = 1078; + + static char BASEDIGIT(int digit) { + return (char)((digit >= 10) ? 'a' - 10 + digit : '0' + digit); + } + + static final int + DTOSTR_STANDARD = 0, /* Either fixed or exponential format; round-trip */ + DTOSTR_STANDARD_EXPONENTIAL = 1, /* Always exponential format; round-trip */ + DTOSTR_FIXED = 2, /* Round to digits after the decimal point; exponential if number is large */ + DTOSTR_EXPONENTIAL = 3, /* Always exponential format; significant digits */ + DTOSTR_PRECISION = 4; /* Either fixed or exponential format; significant digits */ + + + static final int Frac_mask = 0xfffff; + static final int Exp_shift = 20; + static final int Exp_msk1 = 0x100000; + static final int Bias = 1023; + static final int P = 53; + + static final int Exp_shift1 = 20; + static final int Exp_mask = 0x7ff00000; + static final int Bndry_mask = 0xfffff; + static final int Log2P = 1; + + static final int Sign_bit = 0x80000000; + static final int Exp_11 = 0x3ff00000; + static final int Ten_pmax = 22; + static final int Quick_max = 14; + static final int Bletch = 0x10; + static final int Frac_mask1 = 0xfffff; + static final int Int_max = 14; + static final int n_bigtens = 5; + + + static final double tens[] = { + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22 + }; + + static final double bigtens[] = { 1e16, 1e32, 1e64, 1e128, 1e256 }; + + static int lo0bits(int y) + { + int k; + int x = y; + + if ((x & 7) != 0) { + if ((x & 1) != 0) + return 0; + if ((x & 2) != 0) { + return 1; + } + return 2; + } + k = 0; + if ((x & 0xffff) == 0) { + k = 16; + x >>>= 16; + } + if ((x & 0xff) == 0) { + k += 8; + x >>>= 8; + } + if ((x & 0xf) == 0) { + k += 4; + x >>>= 4; + } + if ((x & 0x3) == 0) { + k += 2; + x >>>= 2; + } + if ((x & 1) == 0) { + k++; + x >>>= 1; + if ((x & 1) == 0) + return 32; + } + return k; + } + + /* Return the number (0 through 32) of most significant zero bits in x. */ + static int hi0bits(int x) + { + int k = 0; + + if ((x & 0xffff0000) == 0) { + k = 16; + x <<= 16; + } + if ((x & 0xff000000) == 0) { + k += 8; + x <<= 8; + } + if ((x & 0xf0000000) == 0) { + k += 4; + x <<= 4; + } + if ((x & 0xc0000000) == 0) { + k += 2; + x <<= 2; + } + if ((x & 0x80000000) == 0) { + k++; + if ((x & 0x40000000) == 0) + return 32; + } + return k; + } + + static void stuffBits(byte bits[], int offset, int val) + { + bits[offset] = (byte)(val >> 24); + bits[offset + 1] = (byte)(val >> 16); + bits[offset + 2] = (byte)(val >> 8); + bits[offset + 3] = (byte)(val); + } + + /* Convert d into the form b*2^e, where b is an odd integer. b is the returned + * Bigint and e is the returned binary exponent. Return the number of significant + * bits in b in bits. d must be finite and nonzero. */ + static BigInteger d2b(double d, int[] e, int[] bits) + { + byte dbl_bits[]; + int i, k, y, z, de; + long dBits = Double.doubleToLongBits(d); + int d0 = (int)(dBits >>> 32); + int d1 = (int)(dBits); + + z = d0 & Frac_mask; + d0 &= 0x7fffffff; /* clear sign bit, which we ignore */ + + if ((de = (int)(d0 >>> Exp_shift)) != 0) + z |= Exp_msk1; + + if ((y = d1) != 0) { + dbl_bits = new byte[8]; + k = lo0bits(y); + y >>>= k; + if (k != 0) { + stuffBits(dbl_bits, 4, y | z << (32 - k)); + z >>= k; + } + else + stuffBits(dbl_bits, 4, y); + stuffBits(dbl_bits, 0, z); + i = (z != 0) ? 2 : 1; + } + else { + // JS_ASSERT(z); + dbl_bits = new byte[4]; + k = lo0bits(z); + z >>>= k; + stuffBits(dbl_bits, 0, z); + k += 32; + i = 1; + } + if (de != 0) { + e[0] = de - Bias - (P-1) + k; + bits[0] = P - k; + } + else { + e[0] = de - Bias - (P-1) + 1 + k; + bits[0] = 32*i - hi0bits(z); + } + return new BigInteger(dbl_bits); + } + + public static String JS_dtobasestr(int base, double d) + { + char[] buffer; /* The output string */ + int p; /* index to current position in the buffer */ + int pInt; /* index to the beginning of the integer part of the string */ + + int q; + int digit; + double di; /* d truncated to an integer */ + double df; /* The fractional part of d */ + +// JS_ASSERT(base >= 2 && base <= 36); + + buffer = new char[DTOBASESTR_BUFFER_SIZE]; + + p = 0; + if (d < 0.0) { + buffer[p++] = '-'; + d = -d; + } + + /* Check for Infinity and NaN */ + if (Double.isNaN(d)) + return "NaN"; + else + if (Double.isInfinite(d)) + return "Infinity"; + + /* Output the integer part of d with the digits in reverse order. */ + pInt = p; + di = (int)d; + BigInteger b = BigInteger.valueOf((int)di); + String intDigits = b.toString(base); + intDigits.getChars(0, intDigits.length(), buffer, p); + p += intDigits.length(); + + df = d - di; + if (df != 0.0) { + /* We have a fraction. */ + buffer[p++] = '.'; + + long dBits = Double.doubleToLongBits(d); + int word0 = (int)(dBits >> 32); + int word1 = (int)(dBits); + + int[] e = new int[1]; + int[] bbits = new int[1]; + + b = d2b(df, e, bbits); +// JS_ASSERT(e < 0); + /* At this point df = b * 2^e. e must be less than zero because 0 < df < 1. */ + + int s2 = -(word0 >>> Exp_shift1 & Exp_mask >> Exp_shift1); + if (s2 == 0) + s2 = -1; + s2 += Bias + P; + /* 1/2^s2 = (nextDouble(d) - d)/2 */ +// JS_ASSERT(-s2 < e); + BigInteger mlo = BigInteger.valueOf(1); + BigInteger mhi = mlo; + if ((word1 == 0) && ((word0 & Bndry_mask) == 0) + && ((word0 & (Exp_mask & Exp_mask << 1)) != 0)) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the output string's value is less than d. */ + s2 += Log2P; + mhi = BigInteger.valueOf(1< df = b/2^s2 > 0; + * (d - prevDouble(d))/2 = mlo/2^s2; + * (nextDouble(d) - d)/2 = mhi/2^s2. */ + BigInteger bigBase = BigInteger.valueOf(base); + + boolean done = false; + do { + b = b.multiply(bigBase); + BigInteger[] divResult = b.divideAndRemainder(s); + b = divResult[1]; + digit = (char)(divResult[0].intValue()); + if (mlo == mhi) + mlo = mhi = mlo.multiply(bigBase); + else { + mlo = mlo.multiply(bigBase); + mhi = mhi.multiply(bigBase); + } + + /* Do we yet have the shortest string that will round to d? */ + int j = b.compareTo(mlo); + /* j is b/2^s2 compared with mlo/2^s2. */ + BigInteger delta = s.subtract(mhi); + int j1 = (delta.signum() <= 0) ? 1 : b.compareTo(delta); + /* j1 is b/2^s2 compared with 1 - mhi/2^s2. */ + if (j1 == 0 && ((word1 & 1) == 0)) { + if (j > 0) + digit++; + done = true; + } else + if (j < 0 || (j == 0 && ((word1 & 1) == 0))) { + if (j1 > 0) { + /* Either dig or dig+1 would work here as the least significant digit. + Use whichever would produce an output value closer to d. */ + b = b.shiftLeft(1); + j1 = b.compareTo(s); + if (j1 > 0) /* The even test (|| (j1 == 0 && (digit & 1))) is not here because it messes up odd base output + * such as 3.5 in base 3. */ + digit++; + } + done = true; + } else if (j1 > 0) { + digit++; + done = true; + } +// JS_ASSERT(digit < (uint32)base); + buffer[p++] = BASEDIGIT(digit); + } while (!done); + } + + return new String(buffer, 0, p); + } + + /* dtoa for IEEE arithmetic (dmg): convert double to ASCII string. + * + * Inspired by "How to Print Floating-Point Numbers Accurately" by + * Guy L. Steele, Jr. and Jon L. White [Proc. ACM SIGPLAN '90, pp. 92-101]. + * + * Modifications: + * 1. Rather than iterating, we use a simple numeric overestimate + * to determine k = floor(log10(d)). We scale relevant + * quantities using O(log2(k)) rather than O(k) multiplications. + * 2. For some modes > 2 (corresponding to ecvt and fcvt), we don't + * try to generate digits strictly left to right. Instead, we + * compute with fewer bits and propagate the carry if necessary + * when rounding the final digit up. This is often faster. + * 3. Under the assumption that input will be rounded nearest, + * mode 0 renders 1e23 as 1e23 rather than 9.999999999999999e22. + * That is, we allow equality in stopping tests when the + * round-nearest rule will give the same floating-point value + * as would satisfaction of the stopping test with strict + * inequality. + * 4. We remove common factors of powers of 2 from relevant + * quantities. + * 5. When converting floating-point integers less than 1e16, + * we use floating-point arithmetic rather than resorting + * to multiple-precision integers. + * 6. When asked to produce fewer than 15 digits, we first try + * to get by with floating-point arithmetic; we resort to + * multiple-precision integer arithmetic only if we cannot + * guarantee that the floating-point calculation has given + * the correctly rounded result. For k requested digits and + * "uniformly" distributed input, the probability is + * something like 10^(k-15) that we must resort to the Long + * calculation. + */ + + static int word0(double d) + { + long dBits = Double.doubleToLongBits(d); + return (int)(dBits >> 32); + } + + static double setWord0(double d, int i) + { + long dBits = Double.doubleToLongBits(d); + dBits = ((long)i << 32) | (dBits & 0x0FFFFFFFFL); + return Double.longBitsToDouble(dBits); + } + + static int word1(double d) + { + long dBits = Double.doubleToLongBits(d); + return (int)(dBits); + } + + /* Return b * 5^k. k must be nonnegative. */ + // XXXX the C version built a cache of these + static BigInteger pow5mult(BigInteger b, int k) + { + return b.multiply(BigInteger.valueOf(5).pow(k)); + } + + static boolean roundOff(StringBuffer buf) + { + char lastCh; + while ((lastCh = buf.charAt(buf.length() - 1)) == '9') { + buf.setLength(buf.length() - 1); + if (buf.length() == 0) { + return true; + } + } + buf.append((char)(lastCh + 1)); + return false; + } + + /* Always emits at least one digit. */ + /* If biasUp is set, then rounding in modes 2 and 3 will round away from zero + * when the number is exactly halfway between two representable values. For example, + * rounding 2.5 to zero digits after the decimal point will return 3 and not 2. + * 2.49 will still round to 2, and 2.51 will still round to 3. */ + /* bufsize should be at least 20 for modes 0 and 1. For the other modes, + * bufsize should be two greater than the maximum number of output characters expected. */ + static int + JS_dtoa(double d, int mode, boolean biasUp, int ndigits, + boolean[] sign, StringBuffer buf) + { + /* Arguments ndigits, decpt, sign are similar to those + of ecvt and fcvt; trailing zeros are suppressed from + the returned string. If not null, *rve is set to point + to the end of the return value. If d is +-Infinity or NaN, + then *decpt is set to 9999. + + mode: + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4-9 should give the same return values as 2-3, i.e., + 4 <= mode <= 9 ==> same return as mode + 2 + (mode & 1). These modes are mainly for + debugging; often they run slower but sometimes + faster than modes 2-3. + 4,5,8,9 ==> left-to-right digit generation. + 6-9 ==> don't try fast floating-point estimate + (if applicable). + + Values of mode other than 0-9 are treated as mode 0. + + Sufficient space is allocated to the return value + to hold the suppressed trailing zeros. + */ + + int b2, b5, i, ieps, ilim, ilim0, ilim1, + j, j1, k, k0, m2, m5, s2, s5; + char dig; + long L; + long x; + BigInteger b, b1, delta, mlo, mhi, S; + int[] be = new int[1]; + int[] bbits = new int[1]; + double d2, ds, eps; + boolean spec_case, denorm, k_check, try_quick, leftright; + + if ((word0(d) & Sign_bit) != 0) { + /* set sign for everything, including 0's and NaNs */ + sign[0] = true; + // word0(d) &= ~Sign_bit; /* clear sign bit */ + d = setWord0(d, word0(d) & ~Sign_bit); + } + else + sign[0] = false; + + if ((word0(d) & Exp_mask) == Exp_mask) { + /* Infinity or NaN */ + buf.append(((word1(d) == 0) && ((word0(d) & Frac_mask) == 0)) ? "Infinity" : "NaN"); + return 9999; + } + if (d == 0) { +// no_digits: + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; + } + + b = d2b(d, be, bbits); + if ((i = (int)(word0(d) >>> Exp_shift1 & (Exp_mask>>Exp_shift1))) != 0) { + d2 = setWord0(d, (word0(d) & Frac_mask1) | Exp_11); + /* log(x) ~=~ log(1.5) + (x-1.5)/1.5 + * log10(x) = log(x) / log(10) + * ~=~ log(1.5)/log(10) + (x-1.5)/(1.5*log(10)) + * log10(d) = (i-Bias)*log(2)/log(10) + log10(d2) + * + * This suggests computing an approximation k to log10(d) by + * + * k = (i - Bias)*0.301029995663981 + * + ( (d2-1.5)*0.289529654602168 + 0.176091259055681 ); + * + * We want k to be too large rather than too small. + * The error in the first-order Taylor series approximation + * is in our favor, so we just round up the constant enough + * to compensate for any error in the multiplication of + * (i - Bias) by 0.301029995663981; since |i - Bias| <= 1077, + * and 1077 * 0.30103 * 2^-52 ~=~ 7.2e-14, + * adding 1e-13 to the constant term more than suffices. + * Hence we adjust the constant term to 0.1760912590558. + * (We could get a more accurate k by invoking log10, + * but this is probably not worthwhile.) + */ + i -= Bias; + denorm = false; + } + else { + /* d is denormalized */ + i = bbits[0] + be[0] + (Bias + (P-1) - 1); + x = (i > 32) ? word0(d) << (64 - i) | word1(d) >>> (i - 32) : word1(d) << (32 - i); +// d2 = x; +// word0(d2) -= 31*Exp_msk1; /* adjust exponent */ + d2 = setWord0(x, word0(x) - 31*Exp_msk1); + i -= (Bias + (P-1) - 1) + 1; + denorm = true; + } + /* At this point d = f*2^i, where 1 <= f < 2. d2 is an approximation of f. */ + ds = (d2-1.5)*0.289529654602168 + 0.1760912590558 + i*0.301029995663981; + k = (int)ds; + if (ds < 0.0 && ds != k) + k--; /* want k = floor(ds) */ + k_check = true; + if (k >= 0 && k <= Ten_pmax) { + if (d < tens[k]) + k--; + k_check = false; + } + /* At this point floor(log10(d)) <= k <= floor(log10(d))+1. + If k_check is zero, we're guaranteed that k = floor(log10(d)). */ + j = bbits[0] - i - 1; + /* At this point d = b/2^j, where b is an odd integer. */ + if (j >= 0) { + b2 = 0; + s2 = j; + } + else { + b2 = -j; + s2 = 0; + } + if (k >= 0) { + b5 = 0; + s5 = k; + s2 += k; + } + else { + b2 -= k; + b5 = -k; + s5 = 0; + } + /* At this point d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5), where b is an odd integer, + b2 >= 0, b5 >= 0, s2 >= 0, and s5 >= 0. */ + if (mode < 0 || mode > 9) + mode = 0; + try_quick = true; + if (mode > 5) { + mode -= 4; + try_quick = false; + } + leftright = true; + ilim = ilim1 = 0; + switch(mode) { + case 0: + case 1: + ilim = ilim1 = -1; + i = 18; + ndigits = 0; + break; + case 2: + leftright = false; + /* no break */ + case 4: + if (ndigits <= 0) + ndigits = 1; + ilim = ilim1 = i = ndigits; + break; + case 3: + leftright = false; + /* no break */ + case 5: + i = ndigits + k + 1; + ilim = i; + ilim1 = i - 1; + if (i <= 0) + i = 1; + } + /* ilim is the maximum number of significant digits we want, based on k and ndigits. */ + /* ilim1 is the maximum number of significant digits we want, based on k and ndigits, + when it turns out that k was computed too high by one. */ + + boolean fast_failed = false; + if (ilim >= 0 && ilim <= Quick_max && try_quick) { + + /* Try to get by with floating-point arithmetic. */ + + i = 0; + d2 = d; + k0 = k; + ilim0 = ilim; + ieps = 2; /* conservative */ + /* Divide d by 10^k, keeping track of the roundoff error and avoiding overflows. */ + if (k > 0) { + ds = tens[k&0xf]; + j = k >> 4; + if ((j & Bletch) != 0) { + /* prevent overflows */ + j &= Bletch - 1; + d /= bigtens[n_bigtens-1]; + ieps++; + } + for(; (j != 0); j >>= 1, i++) + if ((j & 1) != 0) { + ieps++; + ds *= bigtens[i]; + } + d /= ds; + } + else if ((j1 = -k) != 0) { + d *= tens[j1 & 0xf]; + for(j = j1 >> 4; (j != 0); j >>= 1, i++) + if ((j & 1) != 0) { + ieps++; + d *= bigtens[i]; + } + } + /* Check that k was computed correctly. */ + if (k_check && d < 1.0 && ilim > 0) { + if (ilim1 <= 0) + fast_failed = true; + else { + ilim = ilim1; + k--; + d *= 10.; + ieps++; + } + } + /* eps bounds the cumulative error. */ +// eps = ieps*d + 7.0; +// word0(eps) -= (P-1)*Exp_msk1; + eps = ieps*d + 7.0; + eps = setWord0(eps, word0(eps) - (P-1)*Exp_msk1); + if (ilim == 0) { + S = mhi = null; + d -= 5.0; + if (d > eps) { + buf.append('1'); + k++; + return k + 1; + } + if (d < -eps) { + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; + } + fast_failed = true; + } + if (!fast_failed) { + fast_failed = true; + if (leftright) { + /* Use Steele & White method of only + * generating digits needed. + */ + eps = 0.5/tens[ilim-1] - eps; + for(i = 0;;) { + L = (long)d; + d -= L; + buf.append((char)('0' + L)); + if (d < eps) { + return k + 1; + } + if (1.0 - d < eps) { +// goto bump_up; + char lastCh; + while (true) { + lastCh = buf.charAt(buf.length() - 1); + buf.setLength(buf.length() - 1); + if (lastCh != '9') break; + if (buf.length() == 0) { + k++; + lastCh = '0'; + break; + } + } + buf.append((char)(lastCh + 1)); + return k + 1; + } + if (++i >= ilim) + break; + eps *= 10.0; + d *= 10.0; + } + } + else { + /* Generate ilim digits, then fix them up. */ + eps *= tens[ilim-1]; + for(i = 1;; i++, d *= 10.0) { + L = (long)d; + d -= L; + buf.append((char)('0' + L)); + if (i == ilim) { + if (d > 0.5 + eps) { +// goto bump_up; + char lastCh; + while (true) { + lastCh = buf.charAt(buf.length() - 1); + buf.setLength(buf.length() - 1); + if (lastCh != '9') break; + if (buf.length() == 0) { + k++; + lastCh = '0'; + break; + } + } + buf.append((char)(lastCh + 1)); + return k + 1; + } + else + if (d < 0.5 - eps) { + while (buf.charAt(buf.length() - 1) == '0') + buf.setLength(buf.length() - 1); +// while(*--s == '0') ; +// s++; + return k + 1; + } + break; + } + } + } + } + if (fast_failed) { + buf.setLength(0); + d = d2; + k = k0; + ilim = ilim0; + } + } + + /* Do we have a "small" integer? */ + + if (be[0] >= 0 && k <= Int_max) { + /* Yes. */ + ds = tens[k]; + if (ndigits < 0 && ilim <= 0) { + S = mhi = null; + if (ilim < 0 || d < 5*ds || (!biasUp && d == 5*ds)) { + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; + } + buf.append('1'); + k++; + return k + 1; + } + for(i = 1;; i++) { + L = (long) (d / ds); + d -= L*ds; + buf.append((char)('0' + L)); + if (i == ilim) { + d += d; + if ((d > ds) || (d == ds && (((L & 1) != 0) || biasUp))) { +// bump_up: +// while(*--s == '9') +// if (s == buf) { +// k++; +// *s = '0'; +// break; +// } +// ++*s++; + char lastCh; + while (true) { + lastCh = buf.charAt(buf.length() - 1); + buf.setLength(buf.length() - 1); + if (lastCh != '9') break; + if (buf.length() == 0) { + k++; + lastCh = '0'; + break; + } + } + buf.append((char)(lastCh + 1)); + } + break; + } + d *= 10.0; + if (d == 0) + break; + } + return k + 1; + } + + m2 = b2; + m5 = b5; + mhi = mlo = null; + if (leftright) { + if (mode < 2) { + i = (denorm) ? be[0] + (Bias + (P-1) - 1 + 1) : 1 + P - bbits[0]; + /* i is 1 plus the number of trailing zero bits in d's significand. Thus, + (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 lsb of d)/10^k. */ + } + else { + j = ilim - 1; + if (m5 >= j) + m5 -= j; + else { + s5 += j -= m5; + b5 += j; + m5 = 0; + } + if ((i = ilim) < 0) { + m2 -= i; + i = 0; + } + /* (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 * 10^(1-ilim))/10^k. */ + } + b2 += i; + s2 += i; + mhi = BigInteger.valueOf(1); + /* (mhi * 2^m2 * 5^m5) / (2^s2 * 5^s5) = one-half of last printed (when mode >= 2) or + input (when mode < 2) significant digit, divided by 10^k. */ + } + /* We still have d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5). Reduce common factors in + b2, m2, and s2 without changing the equalities. */ + if (m2 > 0 && s2 > 0) { + i = (m2 < s2) ? m2 : s2; + b2 -= i; + m2 -= i; + s2 -= i; + } + + /* Fold b5 into b and m5 into mhi. */ + if (b5 > 0) { + if (leftright) { + if (m5 > 0) { + mhi = pow5mult(mhi, m5); + b1 = mhi.multiply(b); + b = b1; + } + if ((j = b5 - m5) != 0) + b = pow5mult(b, j); + } + else + b = pow5mult(b, b5); + } + /* Now we have d/10^k = (b * 2^b2) / (2^s2 * 5^s5) and + (mhi * 2^m2) / (2^s2 * 5^s5) = one-half of last printed or input significant digit, divided by 10^k. */ + + S = BigInteger.valueOf(1); + if (s5 > 0) + S = pow5mult(S, s5); + /* Now we have d/10^k = (b * 2^b2) / (S * 2^s2) and + (mhi * 2^m2) / (S * 2^s2) = one-half of last printed or input significant digit, divided by 10^k. */ + + /* Check for special case that d is a normalized power of 2. */ + spec_case = false; + if (mode < 2) { + if ( (word1(d) == 0) && ((word0(d) & Bndry_mask) == 0) + && ((word0(d) & (Exp_mask & Exp_mask << 1)) != 0) + ) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the decimal output string's value is less than d. */ + b2 += Log2P; + s2 += Log2P; + spec_case = true; + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ + byte [] S_bytes = S.toByteArray(); + int S_hiWord = 0; + for (int idx = 0; idx < 4; idx++) { + S_hiWord = (S_hiWord << 8); + if (idx < S_bytes.length) + S_hiWord |= (S_bytes[idx] & 0xFF); + } + if ((i = (((s5 != 0) ? 32 - hi0bits(S_hiWord) : 1) + s2) & 0x1f) != 0) + i = 32 - i; + /* i is the number of leading zero bits in the most significant word of S*2^s2. */ + if (i > 4) { + i -= 4; + b2 += i; + m2 += i; + s2 += i; + } + else if (i < 4) { + i += 28; + b2 += i; + m2 += i; + s2 += i; + } + /* Now S*2^s2 has exactly four leading zero bits in its most significant word. */ + if (b2 > 0) + b = b.shiftLeft(b2); + if (s2 > 0) + S = S.shiftLeft(s2); + /* Now we have d/10^k = b/S and + (mhi * 2^m2) / S = maximum acceptable error, divided by 10^k. */ + if (k_check) { + if (b.compareTo(S) < 0) { + k--; + b = b.multiply(BigInteger.valueOf(10)); /* we botched the k estimate */ + if (leftright) + mhi = mhi.multiply(BigInteger.valueOf(10)); + ilim = ilim1; + } + } + /* At this point 1 <= d/10^k = b/S < 10. */ + + if (ilim <= 0 && mode > 2) { + /* We're doing fixed-mode output and d is less than the minimum nonzero output in this mode. + Output either zero or the minimum nonzero output depending on which is closer to d. */ + if ((ilim < 0 ) + || ((i = b.compareTo(S = S.multiply(BigInteger.valueOf(5)))) < 0) + || ((i == 0 && !biasUp))) { + /* Always emit at least one digit. If the number appears to be zero + using the current mode, then emit one '0' digit and set decpt to 1. */ + /*no_digits: + k = -1 - ndigits; + goto ret; */ + buf.setLength(0); + buf.append('0'); /* copy "0" to buffer */ + return 1; +// goto no_digits; + } +// one_digit: + buf.append('1'); + k++; + return k + 1; + } + if (leftright) { + if (m2 > 0) + mhi = mhi.shiftLeft(m2); + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi; + if (spec_case) { + mhi = mlo; + mhi = mhi.shiftLeft(Log2P); + } + /* mlo/S = maximum acceptable error, divided by 10^k, if the output is less than d. */ + /* mhi/S = maximum acceptable error, divided by 10^k, if the output is greater than d. */ + + for(i = 1;;i++) { + BigInteger[] divResult = b.divideAndRemainder(S); + b = divResult[1]; + dig = (char)(divResult[0].intValue() + '0'); + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = b.compareTo(mlo); + /* j is b/S compared with mlo/S. */ + delta = S.subtract(mhi); + j1 = (delta.signum() <= 0) ? 1 : b.compareTo(delta); + /* j1 is b/S compared with 1 - mhi/S. */ + if ((j1 == 0) && (mode == 0) && ((word1(d) & 1) == 0)) { + if (dig == '9') { + buf.append('9'); + if (roundOff(buf)) { + k++; + buf.append('1'); + } + return k + 1; +// goto round_9_up; + } + if (j > 0) + dig++; + buf.append(dig); + return k + 1; + } + if ((j < 0) + || ((j == 0) + && (mode == 0) + && ((word1(d) & 1) == 0) + )) { + if (j1 > 0) { + /* Either dig or dig+1 would work here as the least significant decimal digit. + Use whichever would produce a decimal value closer to d. */ + b = b.shiftLeft(1); + j1 = b.compareTo(S); + if (((j1 > 0) || (j1 == 0 && (((dig & 1) == 1) || biasUp))) + && (dig++ == '9')) { + buf.append('9'); + if (roundOff(buf)) { + k++; + buf.append('1'); + } + return k + 1; +// goto round_9_up; + } + } + buf.append(dig); + return k + 1; + } + if (j1 > 0) { + if (dig == '9') { /* possible if i == 1 */ +// round_9_up: +// *s++ = '9'; +// goto roundoff; + buf.append('9'); + if (roundOff(buf)) { + k++; + buf.append('1'); + } + return k + 1; + } + buf.append((char)(dig + 1)); + return k + 1; + } + buf.append(dig); + if (i == ilim) + break; + b = b.multiply(BigInteger.valueOf(10)); + if (mlo == mhi) + mlo = mhi = mhi.multiply(BigInteger.valueOf(10)); + else { + mlo = mlo.multiply(BigInteger.valueOf(10)); + mhi = mhi.multiply(BigInteger.valueOf(10)); + } + } + } + else + for(i = 1;; i++) { +// (char)(dig = quorem(b,S) + '0'); + BigInteger[] divResult = b.divideAndRemainder(S); + b = divResult[1]; + dig = (char)(divResult[0].intValue() + '0'); + buf.append(dig); + if (i >= ilim) + break; + b = b.multiply(BigInteger.valueOf(10)); + } + + /* Round off last digit */ + + b = b.shiftLeft(1); + j = b.compareTo(S); + if ((j > 0) || (j == 0 && (((dig & 1) == 1) || biasUp))) { +// roundoff: +// while(*--s == '9') +// if (s == buf) { +// k++; +// *s++ = '1'; +// goto ret; +// } +// ++*s++; + if (roundOff(buf)) { + k++; + buf.append('1'); + return k + 1; + } + } + else { + /* Strip trailing zeros */ + while (buf.charAt(buf.length() - 1) == '0') + buf.setLength(buf.length() - 1); +// while(*--s == '0') ; +// s++; + } +// ret: +// Bfree(S); +// if (mhi) { +// if (mlo && mlo != mhi) +// Bfree(mlo); +// Bfree(mhi); +// } +// ret1: +// Bfree(b); +// JS_ASSERT(s < buf + bufsize); + return k + 1; + } + + /* Mapping of JSDToStrMode -> JS_dtoa mode */ + private static final int dtoaModes[] = { + 0, /* DTOSTR_STANDARD */ + 0, /* DTOSTR_STANDARD_EXPONENTIAL, */ + 3, /* DTOSTR_FIXED, */ + 2, /* DTOSTR_EXPONENTIAL, */ + 2}; /* DTOSTR_PRECISION */ + + static void + JS_dtostr(StringBuffer buffer, int mode, int precision, double d) + { + int decPt; /* Position of decimal point relative to first digit returned by JS_dtoa */ + boolean[] sign = new boolean[1]; /* true if the sign bit was set in d */ + int nDigits; /* Number of significand digits returned by JS_dtoa */ + +// JS_ASSERT(bufferSize >= (size_t)(mode <= DTOSTR_STANDARD_EXPONENTIAL ? DTOSTR_STANDARD_BUFFER_SIZE : +// DTOSTR_VARIABLE_BUFFER_SIZE(precision))); + + if (mode == DTOSTR_FIXED && (d >= 1e21 || d <= -1e21)) + mode = DTOSTR_STANDARD; /* Change mode here rather than below because the buffer may not be large enough to hold a large integer. */ + + decPt = JS_dtoa(d, dtoaModes[mode], mode >= DTOSTR_FIXED, precision, sign, buffer); + nDigits = buffer.length(); + + /* If Infinity, -Infinity, or NaN, return the string regardless of the mode. */ + if (decPt != 9999) { + boolean exponentialNotation = false; + int minNDigits = 0; /* Minimum number of significand digits required by mode and precision */ + int p; + int q; + + switch (mode) { + case DTOSTR_STANDARD: + if (decPt < -5 || decPt > 21) + exponentialNotation = true; + else + minNDigits = decPt; + break; + + case DTOSTR_FIXED: + if (precision >= 0) + minNDigits = decPt + precision; + else + minNDigits = decPt; + break; + + case DTOSTR_EXPONENTIAL: +// JS_ASSERT(precision > 0); + minNDigits = precision; + /* Fall through */ + case DTOSTR_STANDARD_EXPONENTIAL: + exponentialNotation = true; + break; + + case DTOSTR_PRECISION: +// JS_ASSERT(precision > 0); + minNDigits = precision; + if (decPt < -5 || decPt > precision) + exponentialNotation = true; + break; + } + + /* If the number has fewer than minNDigits, pad it with zeros at the end */ + if (nDigits < minNDigits) { + p = minNDigits; + nDigits = minNDigits; + do { + buffer.append('0'); + } while (buffer.length() != p); + } + + if (exponentialNotation) { + /* Insert a decimal point if more than one significand digit */ + if (nDigits != 1) { + buffer.insert(1, '.'); + } + buffer.append('e'); + if ((decPt - 1) >= 0) + buffer.append('+'); + buffer.append(decPt - 1); +// JS_snprintf(numEnd, bufferSize - (numEnd - buffer), "e%+d", decPt-1); + } else if (decPt != nDigits) { + /* Some kind of a fraction in fixed notation */ +// JS_ASSERT(decPt <= nDigits); + if (decPt > 0) { + /* dd...dd . dd...dd */ + buffer.insert(decPt, '.'); + } else { + /* 0 . 00...00dd...dd */ + for (int i = 0; i < 1 - decPt; i++) + buffer.insert(0, '0'); + buffer.insert(1, '.'); + } + } + } + + /* If negative and neither -0.0 nor NaN, output a leading '-'. */ + if (sign[0] && + !(word0(d) == Sign_bit && word1(d) == 0) && + !((word0(d) & Exp_mask) == Exp_mask && + ((word1(d) != 0) || ((word0(d) & Frac_mask) != 0)))) { + buffer.insert(0, '-'); + } + } + +} + diff --git a/src/org/mozilla/javascript/DebuggableEngineImpl.java b/src/org/mozilla/javascript/DebuggableEngineImpl.java new file mode 100644 index 0000000..3896871 --- /dev/null +++ b/src/org/mozilla/javascript/DebuggableEngineImpl.java @@ -0,0 +1,111 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-2000 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +// API class + +package org.mozilla.javascript; + +import org.mozilla.javascript.debug.*; + +public class DebuggableEngineImpl implements DebuggableEngine { + + public DebuggableEngineImpl(Context cx) { + this.cx = cx; + } + + /** + * Set whether the engine should break when it encounters + * the next line. + *

+ * The engine will call the attached debugger's handleBreakpointHit + * method on the next line it executes if isLineStep is true. + * May be used from another thread to interrupt execution. + * + * @param isLineStep if true, break next line + */ + public void setBreakNextLine(boolean isLineStep) { + cx.inLineStepMode = isLineStep; + } + + /** + * Return the value of the breakNextLine flag. + * @return true if the engine will break on execution of the + * next line. + */ + public boolean getBreakNextLine() { + return cx.inLineStepMode; + } + + /** + * Set the associated debugger. + * @param debugger the debugger to be used on callbacks from + * the engine. + */ + public void setDebugger(Debugger debugger) { + cx.debugger = debugger; + } + + /** + * Return the current debugger. + * @return the debugger, or null if none is attached. + */ + public Debugger getDebugger() { + return cx.debugger; + } + + /** + * Return the number of frames in current execution. + * @return the count of current frames + */ + public int getFrameCount() { + return cx.frameStack == null ? 0 : cx.frameStack.size(); + } + + /** + * Return a frame from the current execution. + * Frames are numbered starting from 0 for the innermost + * frame. + * @param frameNumber the number of the frame in the range + * [0,frameCount-1] + * @return the relevant DebugFrame, or null if frameNumber is out + * of range or the engine isn't currently saving + * frames + */ + public DebugFrame getFrame(int frameNumber) { + return (DebugFrame) cx.frameStack.elementAt(cx.frameStack.size() - frameNumber - 1); + } + + private Context cx; +} diff --git a/src/org/mozilla/javascript/DefaultErrorReporter.java b/src/org/mozilla/javascript/DefaultErrorReporter.java new file mode 100644 index 0000000..10044e8 --- /dev/null +++ b/src/org/mozilla/javascript/DefaultErrorReporter.java @@ -0,0 +1,65 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Norris Boyd + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +package org.mozilla.javascript; + +/** + * This is the default error reporter for JavaScript. + * + * @author Norris Boyd + */ +class DefaultErrorReporter implements ErrorReporter { + + public void warning(String message, String sourceName, int line, + String lineSource, int lineOffset) + { + // do nothing + } + + public void error(String message, String sourceName, int line, + String lineSource, int lineOffset) + { + //System.out.println("error at " + sourceName + ":" + line + " -- " + message); + throw new EvaluatorException(message + " at " + sourceName + ":" + line); + } + + public EvaluatorException runtimeError(String message, String sourceName, + int line, String lineSource, + int lineOffset) + { + //System.out.println("error at " + sourceName + ":" + line + " -- " + message); + return new EvaluatorException(message + " at " + sourceName + ":" + line); + } +} diff --git a/src/org/mozilla/javascript/Delegator.java b/src/org/mozilla/javascript/Delegator.java new file mode 100644 index 0000000..d3e9306 --- /dev/null +++ b/src/org/mozilla/javascript/Delegator.java @@ -0,0 +1,250 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (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.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the + * License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is Delegator.java, released Sep 27, 2000. + * + * The Initial Developer of the Original Code is Matthias Radestock. + * . Portions created by Matthias Radestock are + * Copyright (C) 2000 Matthias Radestock. All Rights Reserved. + * + * Contributor(s): + * Redfig Ltd (http://www.redfig.com) + * LShift Ltd (http://www.lshift.net) + * + * Alternatively, the contents of this file may be used under the terms + * of the GNU Public License (the "GPL License"), in which case the + * provisions of the GPL License are applicable instead of those + * above. If you wish to allow use of your version of this file only + * under the terms of the GPL License and not to allow others to use + * your version of this file under the MPL, indicate your decision by + * deleting the provisions above and replace them with the notice and + * other provisions required by the GPL License. If you do not delete + * the provisions above, a recipient may use your version of this file + * under either the MPL or the GPL License. + */ + +// API class + +package org.mozilla.javascript; + +/** + * This is a helper class for implementing wrappers around Scriptable + * objects. It implements the Function interface and delegates all + * invocations to a delegee Scriptable object. The normal use of this + * class involves creating a sub-class and overriding one or more of + * the methods. + * + * A useful application is the implementation of interceptors, + * pre/post conditions, debugging. + * + * @see Function + * @see Scriptable + * @author Matthias Radestock + */ + +public class Delegator implements Function { + + protected Scriptable obj = null; + + /** + * Create a Delegator prototype. + * + * This constructor should only be used for creating prototype + * objects of Delegator. + * + * @see org.mozilla.javascript.Delegator#construct + */ + public Delegator() { + } + + /** + * Create a new Delegator that forwards requests to a delegee + * Scriptable object. + * + * @param obj the delegee + * @see org.mozilla.javascript.Scriptable + */ + public Delegator(Scriptable obj) { + this.obj = obj; + } + + /** + * Retrieve the delegee. + * + * @return the delegee + */ + public Scriptable getDelegee() { + return obj; + } + /** + * Set the delegee. + * + * @param obj the delegee + * @see org.mozilla.javascript.Scriptable + */ + public void setDelegee(Scriptable obj) { + this.obj = obj; + } + /** + * @see org.mozilla.javascript.Scriptable#getClassName + */ + public String getClassName() { + return obj.getClassName(); + } + /** + * @see org.mozilla.javascript.Scriptable#get + */ + public Object get(String name, Scriptable start) { + return obj.get(name,start); + } + /** + * @see org.mozilla.javascript.Scriptable#get + */ + public Object get(int index, Scriptable start) { + return obj.get(index,start); + } + /** + * @see org.mozilla.javascript.Scriptable#has + */ + public boolean has(String name, Scriptable start) { + return obj.has(name,start); + } + /** + * @see org.mozilla.javascript.Scriptable#has + */ + public boolean has(int index, Scriptable start) { + return obj.has(index,start); + } + /** + * @see org.mozilla.javascript.Scriptable#put + */ + public void put(String name, Scriptable start, Object value) { + obj.put(name,start,value); + } + /** + * @see org.mozilla.javascript.Scriptable#put + */ + public void put(int index, Scriptable start, Object value) { + obj.put(index,start,value); + } + /** + * @see org.mozilla.javascript.Scriptable#delete + */ + public void delete(String name) { + obj.delete(name); + } + /** + * @see org.mozilla.javascript.Scriptable#delete + */ + public void delete(int index) { + obj.delete(index); + } + /** + * @see org.mozilla.javascript.Scriptable#getPrototype + */ + public Scriptable getPrototype() { + return obj.getPrototype(); + } + /** + * @see org.mozilla.javascript.Scriptable#setPrototype + */ + public void setPrototype(Scriptable prototype) { + obj.setPrototype(prototype); + } + /** + * @see org.mozilla.javascript.Scriptable#getParentScope + */ + public Scriptable getParentScope() { + return obj.getParentScope(); + } + /** + * @see org.mozilla.javascript.Scriptable#setParentScope + */ + public void setParentScope(Scriptable parent) { + obj.setParentScope(parent); + } + /** + * @see org.mozilla.javascript.Scriptable#getIds + */ + public Object[] getIds() { + return obj.getIds(); + } + /** + * Note that this method does not get forwarded to the delegee if + * the hint parameter is null, + * ScriptRuntime.ScriptableClass or + * ScriptRuntime.FunctionClass. Instead the object + * itself is returned. + * + * @param hint the type hint + * @return the default value + * + * @see org.mozilla.javascript.Scriptable#getDefaultValue + */ + public Object getDefaultValue(Class hint) { + return (hint == null || + hint == ScriptRuntime.ScriptableClass || + hint == ScriptRuntime.FunctionClass) ? + this : obj.getDefaultValue(hint); + } + /** + * @see org.mozilla.javascript.Scriptable#hasInstance + */ + public boolean hasInstance(Scriptable instance) { + return obj.hasInstance(instance); + } + /** + * @see org.mozilla.javascript.Function#call + */ + public Object call(Context cx, Scriptable scope, Scriptable thisObj, + Object[] args) + throws JavaScriptException { + return ((Function)obj).call(cx,scope,thisObj,args); + } + + /** + * Note that if the delegee is null, + * this method creates a new instance of the Delegator itself + * rathert than forwarding the call to the + * delegee. This permits the use of Delegator + * prototypes. + * + * @param cx the current Context for this thread + * @param scope an enclosing scope of the caller except + * when the function is called from a closure. + * @param args the array of arguments + * @return the allocated object + * @exception JavaScriptException if an uncaught exception + * occurred while executing the constructor + * + * @see org.mozilla.javascript.Function#construct + */ + public Scriptable construct(Context cx, Scriptable scope, Object[] args) + throws JavaScriptException { + if (obj == null) { + //this little trick allows us to declare prototype objects for + //Delegators + try { + Delegator n = (Delegator)this.getClass().newInstance(); + n.setDelegee((Scriptable)args[0]); + return n; + } + catch (Exception e) { + e.printStackTrace(); + System.exit(0); + } + return null; + } + else { + return ((Function)obj).construct(cx,scope,args); + } + } +} diff --git a/src/org/mozilla/javascript/EcmaError.java b/src/org/mozilla/javascript/EcmaError.java new file mode 100644 index 0000000..46c3e2b --- /dev/null +++ b/src/org/mozilla/javascript/EcmaError.java @@ -0,0 +1,152 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * The contents of this file are subject to the Netscape Public + * License Version 1.1 (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.mozilla.org/NPL/ + * + * Software distributed under the License is distributed on an "AS + * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express oqr + * implied. See the License for the specific language governing + * rights and limitations under the License. + * + * The Original Code is Rhino code, released + * May 6, 1999. + * + * The Initial Developer of the Original Code is Netscape + * Communications Corporation. Portions created by Netscape are + * Copyright (C) 1997-1999 Netscape Communications Corporation. All + * Rights Reserved. + * + * Contributor(s): + * Roger Lawrence + * + * Alternatively, the contents of this file may be used under the + * terms of the GNU Public License (the "GPL"), in which case the + * provisions of the GPL are applicable instead of those above. + * If you wish to allow use of your version of this file only + * under the terms of the GPL and not to allow others to use your + * version of this file under the NPL, indicate your decision by + * deleting the provisions above and replace them with the notice + * and other provisions required by the GPL. If you do not delete + * the provisions above, a recipient may use your version of this + * file under either the NPL or the GPL. + */ + +// API class + +package org.mozilla.javascript; + +/** + * The class of exceptions raised by the engine as described in + * ECMA edition 3. See section 15.11.6 in particular. + */ +public class EcmaError extends RuntimeException { + + /** + * Create an exception with the specified detail message. + * + * Errors internal to the JavaScript engine will simply throw a + * RuntimeException. + * + * @param nativeError the NativeError object constructed for this error + * @param sourceName the name of the source reponsible for the error + * @param lineNumber the line number of the source + * @param columnNumber the columnNumber of the source (may be zero if + * unknown) + * @param lineSource the source of the line containing the error (may be + * null if unknown) + */ + public EcmaError(NativeError nativeError, String sourceName, + int lineNumber, int columnNumber, String lineSource) + { + super("EcmaError"); + errorObject = nativeError; + this.sourceName = sourceName; + this.lineNumber = lineNumber; + this.columnNumber = columnNumber; + this.lineSource = lineSource; + } + + /** + * Return a string representation of the error, which currently consists + * of the name of the error together with the message. + */ + public String toString() { + if (sourceName != null && lineNumber > 0) + return errorObject.toString() + " (" + sourceName + + "; line " + lineNumber + ")"; + else + return errorObject.toString(); + } + + /** + * Gets the name of the error. + * + * ECMA edition 3 defines the following + * errors: EvalError, RangeError, ReferenceError, + * SyntaxError, TypeError, and URIError. Additional error names + * may be added in the future. + * + * See ECMA edition 3, 15.11.7.9. + * + * @return the name of the error. + */ + public String getName() { + return errorObject.getName(); + } + + /** + * Gets the message corresponding to the error. + * + * See ECMA edition 3, 15.11.7.10. + * + * @return an implemenation-defined string describing the error. + */ + public String getMessage() { + return errorObject.getMessage(); + } + + /** + * Get the name of the source containing the error, or null + * if that information is not available. + */ + public String getSourceName() { + return sourceName; + } + + /** + * Returns the line number of the statement causing the error, + * or zero if not available. + */ + public int getLineNumber() { + return lineNumber; + } + + /** + * Get the error object corresponding to this exception. + */ + public Scriptable getErrorObject() { + return errorObject; + } + + /** + * The column number of the location of the error, or zero if unknown. + */ + public int getColumnNumber() { + return columnNumber; + } + + /** + * The source of the line causing the error, or zero if unknown. + */ + public String getLineSource() { + return lineSource; + } + + private NativeError errorObject; + private String sourceName; + private int lineNumber; + private int columnNumber; + private String lineSource; +}