diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:28:47 -0800 |
commit | adc854b798c1cfe3bfd4c27d68d5cee38ca617da (patch) | |
tree | 6aed8b4923ca428942cbaa7e848d50237a3d31e0 /archive/src | |
parent | 1c0fed63c71ddb230f3b304aac12caffbedf2f21 (diff) | |
download | libcore-adc854b798c1cfe3bfd4c27d68d5cee38ca617da.zip libcore-adc854b798c1cfe3bfd4c27d68d5cee38ca617da.tar.gz libcore-adc854b798c1cfe3bfd4c27d68d5cee38ca617da.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'archive/src')
80 files changed, 23497 insertions, 0 deletions
diff --git a/archive/src/main/java/java/util/jar/Attributes.java b/archive/src/main/java/java/util/jar/Attributes.java new file mode 100644 index 0000000..5a4d923 --- /dev/null +++ b/archive/src/main/java/java/util/jar/Attributes.java @@ -0,0 +1,548 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.jar; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.harmony.archive.util.Util; + +/** + * The {@code Attributes} class is used to store values for manifest entries. + * Attribute keys are generally instances of {@code Attributes.Name}. Values + * associated with attribute keys are of type {@code String}. + * @since Android 1.0 + */ +public class Attributes implements Cloneable, Map<Object, Object> { + + /** + * The {@code Attributes} as name/value pairs. Maps the attribute names (as + * {@link Attributes.Name}) of a JAR file manifest to arbitrary values. The + * attribute names thus are obtained from the {@link Manifest} for + * convenience. + * + * @since Android 1.0 + */ + protected Map<Object, Object> map; + + /** + * The name part of the name/value pairs constituting an attribute as + * defined by the specification of the JAR manifest. May be composed of the + * following ASCII signs as defined in the EBNF below: + * + * <pre> + * name = alphanum *headerchar + * headerchar = alphanum | - | _ + * alphanum = {A-Z} | {a-z} | {0-9} + * </pre> + * + * @since Android 1.0 + */ + public static class Name { + private final String name; + + private int hashCode; + + /** + * The class path (a main attribute). + * + * @since Android 1.0 + */ + public static final Name CLASS_PATH = new Name("Class-Path"); //$NON-NLS-1$ + + /** + * The version of the manifest file (a main attribute). + * + * @since Android 1.0 + */ + public static final Name MANIFEST_VERSION = new Name( + "Manifest-Version"); //$NON-NLS-1$ + + /** + * The main class's name (for stand-alone applications). + * + * @since Android 1.0 + */ + public static final Name MAIN_CLASS = new Name("Main-Class"); //$NON-NLS-1$ + + /** + * Defines the signature version of the JAR file. + * + * @since Android 1.0 + */ + public static final Name SIGNATURE_VERSION = new Name( + "Signature-Version"); //$NON-NLS-1$ + + /** + * The {@code Content-Type} manifest attribute. + */ + public static final Name CONTENT_TYPE = new Name("Content-Type"); //$NON-NLS-1$ + + /** + * The {@code Sealed} manifest attribute which may have the value + * {@code true} for sealed archives. + * + * @since Android 1.0 + */ + public static final Name SEALED = new Name("Sealed"); //$NON-NLS-1$ + + /** + * The {@code Implementation-Title} attribute whose value is a string + * that defines the title of the extension implementation. + * + * @since Android 1.0 + */ + public static final Name IMPLEMENTATION_TITLE = new Name( + "Implementation-Title"); //$NON-NLS-1$ + + /** + * The {@code Implementation-Version} attribute defining the version of + * the extension implementation. + * + * @since Android 1.0 + */ + public static final Name IMPLEMENTATION_VERSION = new Name( + "Implementation-Version"); //$NON-NLS-1$ + + /** + * The {@code Implementation-Vendor} attribute defining the organization + * that maintains the extension implementation. + * + * @since Android 1.0 + */ + public static final Name IMPLEMENTATION_VENDOR = new Name( + "Implementation-Vendor"); //$NON-NLS-1$ + + /** + * The {@code Specification-Title} attribute defining the title of the + * extension specification. + * + * @since Android 1.0 + */ + public static final Name SPECIFICATION_TITLE = new Name( + "Specification-Title"); //$NON-NLS-1$ + + /** + * The {@code Specification-Version} attribute defining the version of + * the extension specification. + * + * @since Android 1.0 + */ + public static final Name SPECIFICATION_VERSION = new Name( + "Specification-Version"); //$NON-NLS-1$ + + /** + * The {@code Specification-Vendor} attribute defining the organization + * that maintains the extension specification. + * + * @since Android 1.0 + */ + public static final Name SPECIFICATION_VENDOR = new Name( + "Specification-Vendor"); //$NON-NLS-1$ + + /** + * The {@code Extension-List} attribute defining the extensions that are + * needed by the applet. + * + * @since Android 1.0 + */ + public static final Name EXTENSION_LIST = new Name("Extension-List"); //$NON-NLS-1$ + + /** + * The {@code Extension-Name} attribute which defines the unique name of + * the extension. + * + * @since Android 1.0 + */ + public static final Name EXTENSION_NAME = new Name("Extension-Name"); //$NON-NLS-1$ + + /** + * The {@code Extension-Installation} attribute. + * + * @since Android 1.0 + */ + public static final Name EXTENSION_INSTALLATION = new Name( + "Extension-Installation"); //$NON-NLS-1$ + + /** + * The {@code Implementation-Vendor-Id} attribute specifies the vendor + * of an extension implementation if the applet requires an + * implementation from a specific vendor. + * + * @since Android 1.0 + */ + public static final Name IMPLEMENTATION_VENDOR_ID = new Name( + "Implementation-Vendor-Id"); //$NON-NLS-1$ + + /** + * The {@code Implementation-URL} attribute specifying a URL that can be + * used to obtain the most recent version of the extension if the + * required version is not already installed. + * + * @since Android 1.0 + */ + public static final Name IMPLEMENTATION_URL = new Name( + "Implementation-URL"); //$NON-NLS-1$ + + /** + * A String which must satisfy the following EBNF grammar to specify an + * additional attribute: + * + * <pre> + * name = alphanum *headerchar + * headerchar = alphanum | - | _ + * alphanum = {A-Z} | {a-z} | {0-9} + * </pre> + * + * @param s + * The Attribute string. + * @exception IllegalArgumentException + * if the string does not satisfy the EBNF grammar. + * @since Android 1.0 + */ + public Name(String s) { + int i = s.length(); + if (i == 0 || i > 70) { + throw new IllegalArgumentException(); + } + for (; --i >= 0;) { + char ch = s.charAt(i); + if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') + || ch == '_' || ch == '-' || (ch >= '0' && ch <= '9'))) { + throw new IllegalArgumentException(s); + } + } + name = s; + } + + /** + * Returns this attribute name. + * + * @return the attribute name. + * @since Android 1.0 + */ + @Override + public String toString() { + return name; + } + + /** + * returns whether the argument provided is the same as the attribute + * name. + * + * @return if the attribute names correspond. + * @param an + * An attribute name to be compared with this name. + * @since Android 1.0 + */ + @Override + public boolean equals(Object an) { + if (an == null) { + return false; + } + return an.getClass() == this.getClass() + && name.equalsIgnoreCase(((Name) an).name); + } + + /** + * Computes a hash code of the name. + * + * @return the hash value computed from the name. + * @since Android 1.0 + */ + @Override + public int hashCode() { + if (hashCode == 0) { + hashCode = Util.toASCIILowerCase("name").hashCode(); + } + return hashCode; + } + } + + /** + * Constructs an {@code Attributes} instance. + * + * @since Android 1.0 + */ + public Attributes() { + map = new HashMap<Object, Object>(); + } + + /** + * Constructs an {@code Attributes} instance obtaining keys and values from + * the parameter {@code attrib}. + * + * @param attrib + * The attributes to obtain entries from. + * @since Android 1.0 + */ + @SuppressWarnings("unchecked") + public Attributes(Attributes attrib) { + map = (Map<Object, Object>)((HashMap) attrib.map).clone(); + } + + /** + * Constructs an {@code Attributes} instance with initial capacity of size + * {@code size}. + * + * @param size + * Initial size of this {@code Attributes} instance. + * @since Android 1.0 + */ + public Attributes(int size) { + map = new HashMap<Object, Object>(size); + } + + /** + * Removes all key/value pairs from this {@code Attributes}. + * + * @since Android 1.0 + */ + public void clear() { + map.clear(); + } + + /** + * Determines whether this {@code Attributes} contains the specified key. + * + * @param key + * The key to search for. + * @return {@code true} if the key is found, {@code false} otherwise. + * @since Android 1.0 + */ + public boolean containsKey(Object key) { + return map.containsKey(key); + } + + /** + * Determines whether this {@code Attributes} contains the specified value. + * + * @param value + * the value to search for. + * @return {@code true} if the value is found, {@code false} otherwise. + * @since Android 1.0 + */ + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + /** + * Returns a set containing map entries for each of the key/value pair + * contained in this {@code Attributes}. + * + * @return a set of Map.Entry's + * @since Android 1.0 + */ + public Set<Map.Entry<Object, Object>> entrySet() { + return map.entrySet(); + } + + /** + * Returns the value associated with the parameter key. + * + * @param key + * the key to search for. + * @return Object associated with key, or {@code null} if key does not + * exist. + * @since Android 1.0 + */ + public Object get(Object key) { + return map.get(key); + } + + /** + * Determines whether this {@code Attributes} contains any keys. + * + * @return {@code true} if one or more keys exist, {@code false} otherwise. + * @since Android 1.0 + */ + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * Returns a {@code Set} containing all the keys found in this {@code + * Attributes}. + * + * @return a {@code Set} of all keys. + * @since Android 1.0 + */ + public Set<Object> keySet() { + return map.keySet(); + } + + /** + * Stores key/value pairs in this {@code Attributes}. + * + * @param key + * the key to associate with value. + * @param value + * the value to store in this {@code Attributes}. + * @return the value being stored. + * @exception ClassCastException + * when key is not an {@code Attributes.Name} or value is not + * a {@code String}. + *@since Android 1.0 + */ + @SuppressWarnings("cast") // Require cast to force ClassCastException + public Object put(Object key, Object value) { + return map.put((Name)key, (String)value); + } + + /** + * Stores all the key/value pairs in the argument in this {@code Attributes} + * . + * + * @param attrib + * the associations to store (must be of type {@code Attributes} + * ). + * @since Android 1.0 + */ + public void putAll(Map<?, ?> attrib) { + if (attrib == null || !(attrib instanceof Attributes)) { + throw new ClassCastException(); + } + this.map.putAll(attrib); + } + + /** + * Deletes the key/value pair with key {@code key} from this {@code + * Attributes}. + * + * @param key + * the key to remove. + * @return the values associated with the removed key, {@code null} if not + * present. + * @since Android 1.0 + */ + public Object remove(Object key) { + return map.remove(key); + } + + /** + * Returns the number of key/value pairs associated with this {@code + * Attributes}. + * + * @return the size of this {@code Attributes}. + * @since Android 1.0 + */ + public int size() { + return map.size(); + } + + /** + * Returns a collection of all the values present in this {@code Attributes} + * . + * + * @return a collection of all values present. + * @since Android 1.0 + */ + public Collection<Object> values() { + return map.values(); + } + + @SuppressWarnings("unchecked") + @Override + public Object clone() { + Attributes clone; + try { + clone = (Attributes) super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + clone.map = (Map<Object, Object>) ((HashMap) map).clone(); + return clone; + } + + /** + * Returns the hash code of this {@code Attributes}. + * + * @return the hash code of this object. + * @since Android 1.0 + */ + @Override + public int hashCode() { + return map.hashCode(); + } + + /** + * Determines if this {@code Attributes} and the parameter {@code + * Attributes} are equal. Two {@code Attributes} instances are equal if they + * contain the same keys and values. + * + * @param obj + * the object with which this {@code Attributes} is compared. + * @return {@code true} if the {@code Attributes} are equal, {@code false} + * otherwise. + * @since Android 1.0 + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof Attributes) { + return map.equals(((Attributes) obj).map); + } + return false; + } + + /** + * Returns the value associated with the parameter {@code Attributes.Name} + * key. + * + * @param name + * the key to obtain the value for. + * @return the {@code String} associated with name, or {@code null} if name + * is not a valid key. + * @since Android 1.0 + */ + public String getValue(Attributes.Name name) { + return (String) map.get(name); + } + + /** + * Returns the string associated with the parameter name. + * + * @param name + * the key to obtain the value for. + * @return the string associated with name, or {@code null} if name is not a + * valid key. + * @since Android 1.0 + */ + public String getValue(String name) { + return (String) map.get(new Attributes.Name(name)); + } + + /** + * Stores the value {@code val} associated with the key {@code name} in this + * {@code Attributes}. + * + * @param name + * the key to store. + * @param val + * the value to store in this {@code Attributes}. + * @return the value being stored. + * @since Android 1.0 + */ + public String putValue(String name, String val) { + return (String) map.put(new Attributes.Name(name), val); + } +} diff --git a/archive/src/main/java/java/util/jar/InitManifest.java b/archive/src/main/java/java/util/jar/InitManifest.java new file mode 100644 index 0000000..47fdf1c --- /dev/null +++ b/archive/src/main/java/java/util/jar/InitManifest.java @@ -0,0 +1,295 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.jar; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UTFDataFormatException; +import java.security.AccessController; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.harmony.archive.internal.nls.Messages; +import org.apache.harmony.luni.util.PriviAction; +import org.apache.harmony.luni.util.Util; + +class InitManifest { + private final byte[] inbuf = new byte[1024]; + + private int inbufCount = 0, inbufPos = 0; + + private byte[] buffer = new byte[5]; + + private char[] charbuf = new char[0]; + + private final ByteArrayOutputStream out = new ByteArrayOutputStream(256); + + private String encoding; + + private boolean usingUTF8 = true; + + private final Map<String, Attributes.Name> attributeNames = new HashMap<String, Attributes.Name>(); + + private final byte[] mainAttributesChunk; + + InitManifest(InputStream is, Attributes main, Map<String, Attributes> entries, Map<String, byte[]> chunks, + String verString) throws IOException { + encoding = AccessController.doPrivileged(new PriviAction<String>( + "manifest.read.encoding")); //$NON-NLS-1$ + if ("".equals(encoding)) { //$NON-NLS-1$ + encoding = null; + } + + Attributes current = main; + ArrayList<String> list = new ArrayList<String>(); + + // Return the chunk of main attributes in the manifest. + mainAttributesChunk = nextChunk(is, list); + + Iterator<String> it = list.iterator(); + while (it.hasNext()) { + addAttribute(it.next(), current); + } + + // Check for version attribute + if (verString != null && main.getValue(verString) == null) { + throw new IOException(Messages.getString("archive.2D", verString)); //$NON-NLS-1$ + } + + list.clear(); + byte[] chunk = null; + while (chunks == null ? readLines(is, list) : (chunk = nextChunk(is, + list)) != null) { + it = list.iterator(); + String line = it.next(); + if (line.length() < 7 + || !Util.toASCIILowerCase(line.substring(0, 5)).equals("name:")) { //$NON-NLS-1$ + throw new IOException(Messages.getString("archive.23")); //$NON-NLS-1$ + } + // Name: length required space char + String name = line.substring(6, line.length()); + current = new Attributes(12); + if (chunks != null) { + chunks.put(name, chunk); + } + entries.put(name, current); + while (it.hasNext()) { + addAttribute(it.next(), current); + } + list.clear(); + } + + } + + byte[] getMainAttributesChunk() { + return mainAttributesChunk; + } + + private void addLine(int length, List<String> lines) throws IOException { + if (encoding != null) { + lines.add(new String(buffer, 0, length, encoding)); + } else { + if (usingUTF8) { + try { + if (charbuf.length < length) { + charbuf = new char[length]; + } + lines.add(Util.convertUTF8WithBuf(buffer, charbuf, 0, + length)); + } catch (UTFDataFormatException e) { + usingUTF8 = false; + } + } + if (!usingUTF8) { + if (charbuf.length < length) { + charbuf = new char[length]; + } + // If invalid UTF8, convert bytes to chars setting the upper + // bytes to zeros + int charOffset = 0; + int offset = 0; + for (int i = length; --i >= 0;) { + charbuf[charOffset++] = (char) (buffer[offset++] & 0xff); + } + lines.add(new String(charbuf, 0, length)); + } + } + } + + private byte[] nextChunk(InputStream in, List<String> lines) + throws IOException { + if (inbufCount == -1) { + return null; + } + byte next; + int pos = 0; + boolean blankline = false, lastCr = false; + out.reset(); + while (true) { + if (inbufPos == inbufCount) { + if ((inbufCount = in.read(inbuf)) == -1) { + if (out.size() == 0) { + return null; + } + if (blankline) { + addLine(pos, lines); + } + return out.toByteArray(); + } + if (inbufCount == inbuf.length && in.available() == 0) { + /* archive.2E = "line too long" */ + throw new IOException(Messages.getString("archive.2E")); //$NON-NLS-1$ + } + inbufPos = 0; + } + next = inbuf[inbufPos++]; + if (lastCr) { + if (next != '\n') { + inbufPos--; + next = '\r'; + } else { + if (out.size() == 0) { + continue; + } + out.write('\r'); + } + lastCr = false; + } else if (next == '\r') { + lastCr = true; + continue; + } + if (blankline) { + if (next == ' ') { + out.write(next); + blankline = false; + continue; + } + addLine(pos, lines); + if (next == '\n') { + out.write(next); + return out.toByteArray(); + } + pos = 0; + } else if (next == '\n') { + if (out.size() == 0) { + continue; + } + out.write(next); + blankline = true; + continue; + } + blankline = false; + out.write(next); + if (pos == buffer.length) { + byte[] newBuf = new byte[buffer.length * 2]; + System.arraycopy(buffer, 0, newBuf, 0, buffer.length); + buffer = newBuf; + } + buffer[pos++] = next; + } + } + + private boolean readLines(InputStream in, List<String> lines) + throws IOException { + if (inbufCount == -1) { + return false; + } + byte next; + int pos = 0; + boolean blankline = false, lastCr = false; + while (true) { + if (inbufPos == inbufCount) { + if ((inbufCount = in.read(inbuf)) == -1) { + if (blankline) { + addLine(pos, lines); + } + return lines.size() != 0; + } + if (inbufCount == inbuf.length && in.available() == 0) { + /* archive.2E = "line too long" */ + throw new IOException(Messages.getString("archive.2E")); //$NON-NLS-1$ + } + inbufPos = 0; + } + next = inbuf[inbufPos++]; + if (lastCr) { + if (next != '\n') { + inbufPos--; + next = '\r'; + } + lastCr = false; + } else if (next == '\r') { + lastCr = true; + continue; + } + if (blankline) { + if (next == ' ') { + blankline = false; + continue; + } + addLine(pos, lines); + if (next == '\n') { + return true; + } + pos = 0; + } else if (next == '\n') { + if (pos == 0 && lines.size() == 0) { + continue; + } + blankline = true; + continue; + } + blankline = false; + if (pos == buffer.length) { + byte[] newBuf = new byte[buffer.length * 2]; + System.arraycopy(buffer, 0, newBuf, 0, buffer.length); + buffer = newBuf; + } + buffer[pos++] = next; + } + } + + /* Get the next attribute and add it */ + private void addAttribute(String line, Attributes current) + throws IOException { + String header; + int hdrIdx = line.indexOf(':'); + if (hdrIdx < 1) { + throw new IOException(Messages.getString("archive.2F", line)); //$NON-NLS-1$ + } + header = line.substring(0, hdrIdx); + Attributes.Name name = attributeNames.get(header); + if (name == null) { + try { + name = new Attributes.Name(header); + } catch (IllegalArgumentException e) { + throw new IOException(e.toString()); + } + attributeNames.put(header, name); + } + if (hdrIdx + 1 >= line.length() || line.charAt(hdrIdx + 1) != ' ') { + throw new IOException(Messages.getString("archive.2F", line)); //$NON-NLS-1$ + } + // +2 due to required SPACE char + current.put(name, line.substring(hdrIdx + 2, line.length())); + } +} diff --git a/archive/src/main/java/java/util/jar/JarEntry.java b/archive/src/main/java/java/util/jar/JarEntry.java new file mode 100644 index 0000000..b8dabee --- /dev/null +++ b/archive/src/main/java/java/util/jar/JarEntry.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.jar; + +import java.io.IOException; +import java.security.CodeSigner; +import java.security.cert.CertPath; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; +import java.util.zip.ZipEntry; + +import javax.security.auth.x500.X500Principal; + +/** + * Represents a single file in a JAR archive together with the manifest + * attributes and digital signatures associated with it. + * + * @since Android 1.0 + */ +public class JarEntry extends ZipEntry { + private Attributes attributes; + + JarFile parentJar; + + CodeSigner signers[]; + + // Cached factory used to build CertPath-s in <code>getCodeSigners()</code>. + private CertificateFactory factory; + + private boolean isFactoryChecked = false; + + /** + * Creates a new {@code JarEntry} named name. + * + * @param name + * The name of the new {@code JarEntry}. + * @since Android 1.0 + */ + public JarEntry(String name) { + super(name); + } + + /** + * Creates a new {@code JarEntry} using the values obtained from entry. + * + * @param entry + * The ZipEntry to obtain values from. + * @since Android 1.0 + */ + public JarEntry(ZipEntry entry) { + super(entry); + } + + /** + * Returns the {@code Attributes} object associated with this entry or + * {@code null} if none exists. + * + * @return the {@code Attributes} for this entry. + * @exception IOException + * If an error occurs obtaining the {@code Attributes}. + * @see Attributes + * @since Android 1.0 + */ + public Attributes getAttributes() throws IOException { + if (attributes != null || parentJar == null) { + return attributes; + } + Manifest manifest = parentJar.getManifest(); + if (manifest == null) { + return null; + } + return attributes = manifest.getAttributes(getName()); + } + + /** + * Returns an array of {@code Certificate} Objects associated with this + * entry or {@code null} if none exists. Make sure that the everything is + * read from the input stream before calling this method, or else the method + * returns {@code null}. + * + * @return the certificate for this entry. + * @see java.security.cert.Certificate + * @since Android 1.0 + */ + public Certificate[] getCertificates() { + if (null == parentJar) { + return null; + } + JarVerifier jarVerifier = parentJar.verifier; + if (null == jarVerifier) { + return null; + } + return jarVerifier.getCertificates(getName()); + } + + void setAttributes(Attributes attrib) { + attributes = attrib; + } + + /** + * Create a new {@code JarEntry} using the values obtained from the + * argument. + * + * @param je + * The {@code JarEntry} to obtain values from. + * @since Android 1.0 + */ + public JarEntry(JarEntry je) { + super(je); + parentJar = je.parentJar; + attributes = je.attributes; + signers = je.signers; + } + + /** + * Returns the code signers for the digital signatures associated with the + * JAR file. If there is no such code signer, it returns {@code null}. Make + * sure that the everything is read from the input stream before calling + * this method, or else the method returns {@code null}. + * + * @return the code signers for the JAR entry. + * @see CodeSigner + * @since Android 1.0 + */ + public CodeSigner[] getCodeSigners() { + if (null == signers) { + signers = getCodeSigners(getCertificates()); + } + if (null == signers) { + return null; + } + + CodeSigner[] tmp = new CodeSigner[signers.length]; + System.arraycopy(signers, 0, tmp, 0, tmp.length); + return tmp; + } + + private CodeSigner[] getCodeSigners(Certificate[] certs) { + if(null == certs) { + return null; + } + + X500Principal prevIssuer = null; + ArrayList<Certificate> list = new ArrayList<Certificate>(certs.length); + ArrayList<CodeSigner> asigners = new ArrayList<CodeSigner>(); + + for (Certificate element : certs) { + if (!(element instanceof X509Certificate)) { + // Only X509Certificate-s are taken into account - see API spec. + continue; + } + X509Certificate x509 = (X509Certificate) element; + if (null != prevIssuer) { + X500Principal subj = x509.getSubjectX500Principal(); + if (!prevIssuer.equals(subj)) { + // Ok, this ends the previous chain, + // so transform this one into CertPath ... + addCodeSigner(asigners, list); + // ... and start a new one + list.clear(); + }// else { it's still the same chain } + + } + prevIssuer = x509.getIssuerX500Principal(); + list.add(x509); + } + if (!list.isEmpty()) { + addCodeSigner(asigners, list); + } + if (asigners.isEmpty()) { + // 'signers' is 'null' already + return null; + } + + CodeSigner[] tmp = new CodeSigner[asigners.size()]; + asigners.toArray(tmp); + return tmp; + + } + + private void addCodeSigner(ArrayList<CodeSigner> asigners, + List<Certificate> list) { + CertPath certPath = null; + if (!isFactoryChecked) { + try { + factory = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$ + } catch (CertificateException ex) { + // do nothing + } finally { + isFactoryChecked = true; + } + } + if (null == factory) { + return; + } + try { + certPath = factory.generateCertPath(list); + } catch (CertificateException ex) { + // do nothing + } + if (null != certPath) { + asigners.add(new CodeSigner(certPath, null)); + } + } +} diff --git a/archive/src/main/java/java/util/jar/JarException.java b/archive/src/main/java/java/util/jar/JarException.java new file mode 100644 index 0000000..f18d639 --- /dev/null +++ b/archive/src/main/java/java/util/jar/JarException.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.jar; + +import java.util.zip.ZipException; + +/** + * This runtime exception is thrown when a problem occurs while reading a JAR + * file. + * + * @since Android 1.0 + */ +public class JarException extends ZipException { + + private static final long serialVersionUID = 7159778400963954473L; + + /** + * Constructs a new {@code JarException} instance. + * + * @since Android 1.0 + */ + public JarException() { + super(); + } + + /** + * Constructs a new {@code JarException} instance with the specified + * message. + * + * @param detailMessage + * the detail message for the exception. + * @since Android 1.0 + */ + public JarException(String detailMessage) { + super(detailMessage); + } +} diff --git a/archive/src/main/java/java/util/jar/JarFile.java b/archive/src/main/java/java/util/jar/JarFile.java new file mode 100644 index 0000000..9af9056 --- /dev/null +++ b/archive/src/main/java/java/util/jar/JarFile.java @@ -0,0 +1,478 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.jar; + +// BEGIN android-removed +// import java.io.ByteArrayInputStream; +// END android-removed +// BEGIN android-added +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.ArrayList; +// END android-added +import java.io.File; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.security.MessageDigest; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import org.apache.harmony.archive.util.Util; + +/** + * {@code JarFile} is used to read jar entries and their associated data from + * jar files. + * + * @see JarInputStream + * @see JarEntry + * @since Android 1.0 + */ +public class JarFile extends ZipFile { + + /** + * The MANIFEST file name. + * + * @since Android 1.0 + */ + public static final String MANIFEST_NAME = "META-INF/MANIFEST.MF"; //$NON-NLS-1$ + + static final String META_DIR = "META-INF/"; //$NON-NLS-1$ + + private Manifest manifest; + + private ZipEntry manifestEntry; + + JarVerifier verifier; + + // BEGIN android-added + private boolean closed = false; + // END android-added + + static final class JarFileInputStream extends FilterInputStream { + private long count; + + private ZipEntry zipEntry; + + private JarVerifier verifier; + + private JarVerifier.VerifierEntry entry; + + private MessageDigest digest; + + JarFileInputStream(InputStream is, ZipEntry ze, JarVerifier ver) { + super(is); + if (ver != null) { + zipEntry = ze; + verifier = ver; + count = zipEntry.getSize(); + entry = verifier.initEntry(ze.getName()); + if (entry != null) { + digest = entry.digest; + } + } + } + + @Override + public int read() throws IOException { + int r = super.read(); + if (entry != null) { + if (r != -1) { + digest.update((byte) r); + count--; + } + if (r == -1 || count <= 0) { + JarVerifier.VerifierEntry temp = entry; + entry = null; + verifier.verifySignatures(temp, zipEntry); + } + } + return r; + } + + @Override + public int read(byte[] buf, int off, int nbytes) throws IOException { + int r = super.read(buf, off, nbytes); + if (entry != null) { + if (r != -1) { + int size = r; + if (count < size) { + size = (int) count; + } + digest.update(buf, off, size); + count -= r; + } + if (r == -1 || count <= 0) { + JarVerifier.VerifierEntry temp = entry; + entry = null; + verifier.verifySignatures(temp, zipEntry); + } + } + return r; + } + + @Override + public long skip(long nbytes) throws IOException { + long cnt = 0, rem = 0; + byte[] buf = new byte[4096]; + while (cnt < nbytes) { + int x = read(buf, 0, + (rem = nbytes - cnt) > buf.length ? buf.length + : (int) rem); + if (x == -1) { + return cnt; + } + cnt += x; + } + return cnt; + } + } + + /** + * Create a new {@code JarFile} using the contents of the specified file. + * + * @param file + * the JAR file as {@link File}. + * @throws IOException + * If the file cannot be read. + * @since Android 1.0 + */ + public JarFile(File file) throws IOException { + this(file, true); + } + + /** + * Create a new {@code JarFile} using the contents of the specified file. + * + * @param file + * the JAR file as {@link File}. + * @param verify + * if this JAR file is signed whether it must be verified. + * @throws IOException + * If the file cannot be read. + * @since Android 1.0 + */ + public JarFile(File file, boolean verify) throws IOException { + super(file); + if (verify) { + verifier = new JarVerifier(file.getPath()); + } + readMetaEntries(); + } + + /** + * Create a new {@code JarFile} using the contents of file. + * + * @param file + * the JAR file as {@link File}. + * @param verify + * if this JAR filed is signed whether it must be verified. + * @param mode + * the mode to use, either {@link ZipFile#OPEN_READ OPEN_READ} or + * {@link ZipFile#OPEN_DELETE OPEN_DELETE}. + * @throws IOException + * If the file cannot be read. + * @since Android 1.0 + */ + public JarFile(File file, boolean verify, int mode) throws IOException { + super(file, mode); + if (verify) { + verifier = new JarVerifier(file.getPath()); + } + readMetaEntries(); + } + + /** + * Create a new {@code JarFile} from the contents of the file specified by + * filename. + * + * @param filename + * the file name referring to the JAR file. + * @throws IOException + * if file name cannot be opened for reading. + * @since Android 1.0 + */ + public JarFile(String filename) throws IOException { + this(filename, true); + + } + + /** + * Create a new {@code JarFile} from the contents of the file specified by + * {@code filename}. + * + * @param filename + * the file name referring to the JAR file. + * @param verify + * if this JAR filed is signed whether it must be verified. + * @throws IOException + * If file cannot be opened or read. + * @since Android 1.0 + */ + public JarFile(String filename, boolean verify) throws IOException { + super(filename); + if (verify) { + verifier = new JarVerifier(filename); + } + readMetaEntries(); + } + + /** + * Return an enumeration containing the {@code JarEntrys} contained in this + * {@code JarFile}. + * + * @return the {@code Enumeration} containing the JAR entries. + * @throws IllegalStateException + * if this {@code JarFile} is closed. + * @since Android 1.0 + */ + @Override + public Enumeration<JarEntry> entries() { + class JarFileEnumerator implements Enumeration<JarEntry> { + Enumeration<? extends ZipEntry> ze; + + JarFile jf; + + JarFileEnumerator(Enumeration<? extends ZipEntry> zenum, JarFile jf) { + ze = zenum; + this.jf = jf; + } + + public boolean hasMoreElements() { + return ze.hasMoreElements(); + } + + public JarEntry nextElement() { + JarEntry je = new JarEntry(ze.nextElement()); + je.parentJar = jf; + return je; + } + } + return new JarFileEnumerator(super.entries(), this); + } + + /** + * Return the {@code JarEntry} specified by its name or {@code null} if no + * such entry exists. + * + * @param name + * the name of the entry in the JAR file. + * @return the JAR entry defined by the name. + * @since Android 1.0 + */ + public JarEntry getJarEntry(String name) { + return (JarEntry) getEntry(name); + } + + + // BEGIN android-added + private byte[] getAllBytesFromStreamAndClose(InputStream is) throws IOException { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + try { + byte[] buf = new byte[666]; + int iRead; int off; + while (is.available() > 0) { + iRead = is.read(buf, 0, buf.length); + if (iRead > 0) + bs.write(buf, 0, iRead); + } + } finally { + is.close(); + } + return bs.toByteArray(); + } + // END android-added + + /** + * Returns the {@code Manifest} object associated with this {@code JarFile} + * or {@code null} if no MANIFEST entry exists. + * + * @return the MANIFEST. + * @throws IOException + * if an error occurs reading the MANIFEST file. + * @throws IllegalStateException + * if the jar file is closed. + * @see Manifest + * @since Android 1.0 + */ + public Manifest getManifest() throws IOException { + // BEGIN android-added + if (closed) { + throw new IllegalStateException("JarFile has been closed."); + } + // END android-added + if (manifest != null) { + return manifest; + } + try { + // BEGIN android-modified + InputStream is = super.getInputStream(manifestEntry); + if (verifier != null) { + verifier.addMetaEntry(manifestEntry.getName(), getAllBytesFromStreamAndClose(is)); + is = super.getInputStream(manifestEntry); + } + // END android-modified + try { + manifest = new Manifest(is, verifier != null); + } finally { + is.close(); + } + manifestEntry = null; + } catch(NullPointerException e) { + manifestEntry = null; + } + return manifest; + } + + private void readMetaEntries() throws IOException { + ZipEntry[] metaEntries = getMetaEntriesImpl(null); + int dirLength = META_DIR.length(); + + boolean signed = false; + + if (null != metaEntries) { + for (ZipEntry entry : metaEntries) { + String entryName = entry.getName(); + if (manifestEntry == null + && manifest == null + && Util.ASCIIIgnoreCaseRegionMatches(entryName, + dirLength, MANIFEST_NAME, dirLength, + MANIFEST_NAME.length() - dirLength)) { + manifestEntry = entry; + if (verifier == null) { + break; + } + } else if (verifier != null && entryName.length() > dirLength + && (Util.ASCIIIgnoreCaseRegionMatches(entryName, entryName.length() - 3, ".SF", 0 ,3) //$NON-NLS-1$ + || Util.ASCIIIgnoreCaseRegionMatches(entryName, entryName.length() - 4, ".DSA", 0 ,4) //$NON-NLS-1$ + || Util.ASCIIIgnoreCaseRegionMatches(entryName, entryName.length() - 4, ".RSA", 0 ,4))){ //$NON-NLS-1$ + signed = true; + InputStream is = super.getInputStream(entry); + // BEGIN android-modified + byte[] buf = getAllBytesFromStreamAndClose(is); + // END android-modified + verifier.addMetaEntry(entryName, buf); + } + } + } + if (!signed) { + verifier = null; + } + } + + /** + * Return an {@code InputStream} for reading the decompressed contents of + * ZIP entry. + * + * @param ze + * the ZIP entry to be read. + * @return the input stream to read from. + * @throws IOException + * if an error occurred while creating the input stream. + * @since Android 1.0 + */ + @Override + public InputStream getInputStream(ZipEntry ze) throws IOException { + if (manifestEntry != null) { + getManifest(); + } + if (verifier != null) { + verifier.setManifest(getManifest()); + if (manifest != null) { + verifier.mainAttributesChunk = manifest + .getMainAttributesChunk(); + } + if (verifier.readCertificates()) { + verifier.removeMetaEntries(); + if (manifest != null) { + manifest.removeChunks(); + } + if (!verifier.isSignedJar()) { + verifier = null; + } + } + } + InputStream in = super.getInputStream(ze); + if (in == null) { + return null; + } + return new JarFileInputStream(in, ze, ze.getSize() >= 0 ? verifier + : null); + } + + /** + * Return the {@code JarEntry} specified by name or {@code null} if no such + * entry exists. + * + * @param name + * the name of the entry in the JAR file. + * @return the ZIP entry extracted. + * @since Android 1.0 + */ + @Override + public ZipEntry getEntry(String name) { + ZipEntry ze = super.getEntry(name); + if (ze == null) { + return ze; + } + JarEntry je = new JarEntry(ze); + je.parentJar = this; + return je; + } + + // BEGIN android-modified + private ZipEntry[] getMetaEntriesImpl(byte[] buf) { + int n = 0; + + List<ZipEntry> list = new ArrayList<ZipEntry>(); + + Enumeration<? extends ZipEntry> allEntries = entries(); + while (allEntries.hasMoreElements()) { + ZipEntry ze = allEntries.nextElement(); + if (ze.getName().startsWith("META-INF/") && ze.getName().length() > 9) { + list.add(ze); + } + } + if (list.size() != 0) { + ZipEntry[] result = new ZipEntry[list.size()]; + list.toArray(result); + return result; + } + else { + return null; + } + } + // END android-modified + + // BEGIN android-added + /** + * Closes this {@code JarFile}. + * + * @throws IOException + * if an error occurs. + * @since Android 1.0 + */ + @Override + public void close() throws IOException { + super.close(); + closed = true; + } + // END android-added + +} diff --git a/archive/src/main/java/java/util/jar/JarInputStream.java b/archive/src/main/java/java/util/jar/JarInputStream.java new file mode 100644 index 0000000..ef353ab --- /dev/null +++ b/archive/src/main/java/java/util/jar/JarInputStream.java @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.jar; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import org.apache.harmony.archive.util.Util; + +/** + * The input stream from which the JAR file to be read may be fetched. It is + * used like the {@code ZipInputStream}. + * + * @see ZipInputStream + * @since Android 1.0 + */ +public class JarInputStream extends ZipInputStream { + + private Manifest manifest; + + private boolean eos = false; + + private JarEntry mEntry; + + private JarEntry jarEntry; + + private boolean isMeta; + + private JarVerifier verifier; + + private OutputStream verStream; + + /** + * Constructs a new {@code JarInputStream} from an input stream. + * + * @param stream + * the input stream containing the JAR file. + * @param verify + * if the file should be verified with a {@code JarVerifier}. + * @throws IOException + * If an error occurs reading entries from the input stream. + * @see ZipInputStream#ZipInputStream(InputStream) + * @since Android 1.0 + */ + public JarInputStream(InputStream stream, boolean verify) + throws IOException { + super(stream); + if (verify) { + verifier = new JarVerifier("JarInputStream"); //$NON-NLS-1$ + } + if ((mEntry = getNextJarEntry()) == null) { + return; + } + String name = Util.toASCIIUpperCase(mEntry.getName()); + if (name.equals(JarFile.META_DIR)) { + mEntry = null; // modifies behavior of getNextJarEntry() + closeEntry(); + mEntry = getNextJarEntry(); + name = mEntry.getName().toUpperCase(); + } + if (name.equals(JarFile.MANIFEST_NAME)) { + mEntry = null; + manifest = new Manifest(this, verify); + closeEntry(); + if (verify) { + verifier.setManifest(manifest); + if (manifest != null) { + verifier.mainAttributesChunk = manifest + .getMainAttributesChunk(); + } + } + + } else { + Attributes temp = new Attributes(3); + temp.map.put("hidden", null); //$NON-NLS-1$ + mEntry.setAttributes(temp); + /* + * if not from the first entry, we will not get enough + * information,so no verify will be taken out. + */ + verifier = null; + } + } + + /** + * Constructs a new {@code JarInputStream} from an input stream. + * + * @param stream + * the input stream containing the JAR file. + * @throws IOException + * If an error occurs reading entries from the input stream. + * @see ZipInputStream#ZipInputStream(InputStream) + * @since Android 1.0 + */ + public JarInputStream(InputStream stream) throws IOException { + this(stream, true); + } + + /** + * Returns the {@code Manifest} object associated with this {@code + * JarInputStream} or {@code null} if no manifest entry exists. + * + * @return the MANIFEST specifying the contents of the JAR file. + * @since Android 1.0 + */ + public Manifest getManifest() { + return manifest; + } + + /** + * Returns the next {@code JarEntry} contained in this stream or {@code + * null} if no more entries are present. + * + * @return the next JAR entry. + * @throws IOException + * if an error occurs while reading the entry. + * @since Android 1.0 + */ + public JarEntry getNextJarEntry() throws IOException { + return (JarEntry) getNextEntry(); + } + + /** + * Reads up to {@code length} of decompressed data and stores it in + * {@code buffer} starting at {@code offset}. + * + * @param buffer + * Buffer to store into + * @param offset + * offset in buffer to store at + * @param length + * number of bytes to store + * @return Number of uncompressed bytes read + * @throws IOException + * if an IOException occurs. + * @since Android 1.0 + */ + @Override + public int read(byte[] buffer, int offset, int length) throws IOException { + if (mEntry != null) { + return -1; + } + int r = super.read(buffer, offset, length); + if (verStream != null && !eos) { + if (r == -1) { + eos = true; + if (verifier != null) { + if (isMeta) { + verifier.addMetaEntry(jarEntry.getName(), + ((ByteArrayOutputStream) verStream) + .toByteArray()); + try { + verifier.readCertificates(); + } catch (SecurityException e) { + verifier = null; + throw e; + } + } else { + verifier.verifySignatures( + (JarVerifier.VerifierEntry) verStream, + jarEntry); + } + } + } else { + verStream.write(buffer, offset, r); + } + } + return r; + } + + /** + * Returns the next {@code ZipEntry} contained in this stream or {@code + * null} if no more entries are present. + * + * @return the next extracted ZIP entry. + * @throws IOException + * if an error occurs while reading the entry. + * @since Android 1.0 + */ + @Override + public ZipEntry getNextEntry() throws IOException { + if (mEntry != null) { + jarEntry = mEntry; + mEntry = null; + jarEntry.setAttributes(null); + } else { + jarEntry = (JarEntry) super.getNextEntry(); + if (jarEntry == null) { + return null; + } + if (verifier != null) { + isMeta = Util.toASCIIUpperCase(jarEntry.getName()).startsWith( + JarFile.META_DIR); + if (isMeta) { + verStream = new ByteArrayOutputStream(); + } else { + verStream = verifier.initEntry(jarEntry.getName()); + } + } + } + eos = false; + return jarEntry; + } + + @Override + protected ZipEntry createZipEntry(String name) { + JarEntry entry = new JarEntry(name); + if (manifest != null) { + entry.setAttributes(manifest.getAttributes(name)); + } + return entry; + } +} diff --git a/archive/src/main/java/java/util/jar/JarOutputStream.java b/archive/src/main/java/java/util/jar/JarOutputStream.java new file mode 100644 index 0000000..640f4ef --- /dev/null +++ b/archive/src/main/java/java/util/jar/JarOutputStream.java @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.jar; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * The {@code JarOutputStream} is used to write data in the {@code JarFile} + * format to an arbitrary output stream + * + * @since Android 1.0 + */ +public class JarOutputStream extends ZipOutputStream { + + private Manifest manifest; + + /** + * Constructs a new {@code JarOutputStream} using an output stream. The + * content of the {@code Manifest} must match the JAR entry information + * written subsequently to the stream. + * + * @param os + * the {@code OutputStream} to write to + * @param mf + * the {@code Manifest} to output for this JAR file. + * @throws IOException + * if an error occurs creating the {@code JarOutputStream}. + */ + public JarOutputStream(OutputStream os, Manifest mf) throws IOException { + super(os); + if (mf == null) { + throw new NullPointerException(); + } + manifest = mf; + ZipEntry ze = new ZipEntry(JarFile.MANIFEST_NAME); + putNextEntry(ze); + manifest.write(this); + closeEntry(); + } + + /** + * Constructs a new {@code JarOutputStream} using an arbitrary output + * stream. + * + * @param os + * the {@code OutputStream} to write to. + * @throws IOException + * if an error occurs creating the {@code JarOutputStream}. + */ + @SuppressWarnings("unused") + public JarOutputStream(OutputStream os) throws IOException { + super(os); + } + + /** + * Writes the specified ZIP entry to the underlying stream. The previous + * entry is closed if it is still open. + * + * @param ze + * the {@code ZipEntry} to write to. + * @throws IOException + * if an error occurs writing to the entry. + * @see ZipEntry + * @since Android 1.0 + */ + @Override + public void putNextEntry(ZipEntry ze) throws IOException { + super.putNextEntry(ze); + } +} diff --git a/archive/src/main/java/java/util/jar/JarVerifier.java b/archive/src/main/java/java/util/jar/JarVerifier.java new file mode 100644 index 0000000..b9173f2 --- /dev/null +++ b/archive/src/main/java/java/util/jar/JarVerifier.java @@ -0,0 +1,510 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.jar; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.zip.ZipEntry; + +import org.apache.harmony.archive.internal.nls.Messages; +import org.apache.harmony.luni.util.Base64; +import org.apache.harmony.security.utils.JarUtils; + +import org.apache.harmony.archive.util.Util; + +// BEGIN android-added +import org.apache.harmony.xnet.provider.jsse.OpenSSLMessageDigestJDK; +// END android-added + +/** + * Non-public class used by {@link JarFile} and {@link JarInputStream} to manage + * the verification of signed JARs. {@code JarFile} and {@code JarInputStream} + * objects are expected to have a {@code JarVerifier} instance member which + * can be used to carry out the tasks associated with verifying a signed JAR. + * These tasks would typically include: + * <ul> + * <li>verification of all signed signature files + * <li>confirmation that all signed data was signed only by the party or parties + * specified in the signature block data + * <li>verification that the contents of all signature files (i.e. {@code .SF} + * files) agree with the JAR entries information found in the JAR manifest. + * </ul> + */ +class JarVerifier { + + private final String jarName; + + private Manifest man; + + private HashMap<String, byte[]> metaEntries = new HashMap<String, byte[]>(5); + + private final Hashtable<String, HashMap<String, Attributes>> signatures = + new Hashtable<String, HashMap<String, Attributes>>(5); + + private final Hashtable<String, Certificate[]> certificates = + new Hashtable<String, Certificate[]>(5); + + private final Hashtable<String, Certificate[]> verifiedEntries = + new Hashtable<String, Certificate[]>(); + + byte[] mainAttributesChunk; + + // BEGIN android-added + private static long measureCount = 0; + + private static long averageTime = 0; + // END android-added + + /** + * TODO Type description + */ + static class VerifierEntry extends OutputStream { + + MessageDigest digest; + + byte[] hash; + + Certificate[] certificates; + + VerifierEntry(MessageDigest digest, byte[] hash, + Certificate[] certificates) { + this.digest = digest; + this.hash = hash; + this.certificates = certificates; + } + + /* + * (non-Javadoc) + * + * @see java.io.OutputStream#write(int) + */ + @Override + public void write(int value) { + digest.update((byte) value); + } + + /* + * (non-Javadoc) + * + * @see java.io.OutputStream#write(byte[], int, int) + */ + @Override + public void write(byte[] buf, int off, int nbytes) { + digest.update(buf, off, nbytes); + } + } + + /** + * Constructs and returns a new instance of {@code JarVerifier}. + * + * @param name + * the name of the JAR file being verified. + */ + JarVerifier(String name) { + jarName = name; + } + + /** + * Invoked for each new JAR entry read operation from the input + * stream. This method constructs and returns a new {@link VerifierEntry} + * which contains the certificates used to sign the entry and its hash value + * as specified in the JAR MANIFEST format. + * + * @param name + * the name of an entry in a JAR file which is <b>not</b> in the + * {@code META-INF} directory. + * @return a new instance of {@link VerifierEntry} which can be used by + * callers as an {@link OutputStream}. + * @since Android 1.0 + */ + VerifierEntry initEntry(String name) { + // If no manifest is present by the time an entry is found, + // verification cannot occur. If no signature files have + // been found, do not verify. + if (man == null || signatures.size() == 0) { + return null; + } + + Attributes attributes = man.getAttributes(name); + // entry has no digest + if (attributes == null) { + return null; + } + + Vector<Certificate> certs = new Vector<Certificate>(); + Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = + signatures.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, HashMap<String, Attributes>> entry = it.next(); + HashMap<String, Attributes> hm = entry.getValue(); + if (hm.get(name) != null) { + // Found an entry for entry name in .SF file + String signatureFile = entry.getKey(); + + Vector<Certificate> newCerts = getSignerCertificates( + signatureFile, certificates); + Iterator<Certificate> iter = newCerts.iterator(); + while (iter.hasNext()) { + certs.add(iter.next()); + } + } + } + + // entry is not signed + if (certs.size() == 0) { + return null; + } + Certificate[] certificatesArray = new Certificate[certs.size()]; + certs.toArray(certificatesArray); + + String algorithms = attributes.getValue("Digest-Algorithms"); //$NON-NLS-1$ + if (algorithms == null) { + algorithms = "SHA SHA1"; //$NON-NLS-1$ + } + StringTokenizer tokens = new StringTokenizer(algorithms); + while (tokens.hasMoreTokens()) { + String algorithm = tokens.nextToken(); + String hash = attributes.getValue(algorithm + "-Digest"); //$NON-NLS-1$ + if (hash == null) { + continue; + } + byte[] hashBytes; + try { + hashBytes = hash.getBytes("ISO8859_1"); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e.toString()); + } + + try { + // BEGIN android-changed + return new VerifierEntry(OpenSSLMessageDigestJDK.getInstance(algorithm), + hashBytes, certificatesArray); + // END android-changed + } catch (NoSuchAlgorithmException e) { + // Ignored + } + } + return null; + } + + /** + * Add a new meta entry to the internal collection of data held on each JAR + * entry in the {@code META-INF} directory including the manifest + * file itself. Files associated with the signing of a JAR would also be + * added to this collection. + * + * @param name + * the name of the file located in the {@code META-INF} + * directory. + * @param buf + * the file bytes for the file called {@code name}. + * @see #removeMetaEntries() + */ + void addMetaEntry(String name, byte[] buf) { + metaEntries.put(Util.toASCIIUpperCase(name), buf); + } + + /** + * If the associated JAR file is signed, check on the validity of all of the + * known signatures. + * + * @return {@code true} if the associated JAR is signed and an internal + * check verifies the validity of the signature(s). {@code false} if + * the associated JAR file has no entries at all in its {@code + * META-INF} directory. This situation is indicative of an invalid + * JAR file. + * <p> + * Will also return {@code true} if the JAR file is <i>not</i> + * signed. + * </p> + * @throws SecurityException + * if the JAR file is signed and it is determined that a + * signature block file contains an invalid signature for the + * corresponding signature file. + * @since Android 1.0 + */ + synchronized boolean readCertificates() { + if (metaEntries == null) { + return false; + } + Iterator<String> it = metaEntries.keySet().iterator(); + while (it.hasNext()) { + String key = it.next(); + if (key.endsWith(".DSA") || key.endsWith(".RSA")) { //$NON-NLS-1$ //$NON-NLS-2$ + verifyCertificate(key); + // Check for recursive class load + if (metaEntries == null) { + return false; + } + it.remove(); + } + } + return true; + } + + /** + * @param certFile + */ + private void verifyCertificate(String certFile) { + // Found Digital Sig, .SF should already have been read + String signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + + ".SF"; //$NON-NLS-1$ + byte[] sfBytes = metaEntries.get(signatureFile); + if (sfBytes == null) { + return; + } + + byte[] sBlockBytes = metaEntries.get(certFile); + try { + Certificate[] signerCertChain = JarUtils.verifySignature( + new ByteArrayInputStream(sfBytes), + new ByteArrayInputStream(sBlockBytes)); + /* + * Recursive call in loading security provider related class which + * is in a signed JAR. + */ + if (null == metaEntries) { + return; + } + if (signerCertChain != null) { + certificates.put(signatureFile, signerCertChain); + } + } catch (IOException e) { + return; + } catch (GeneralSecurityException e) { + /* [MSG "archive.30", "{0} failed verification of {1}"] */ + throw new SecurityException( + Messages.getString("archive.30", jarName, signatureFile)); //$NON-NLS-1$ + } + + // Verify manifest hash in .sf file + Attributes attributes = new Attributes(); + HashMap<String, Attributes> hm = new HashMap<String, Attributes>(); + try { + new InitManifest(new ByteArrayInputStream(sfBytes), attributes, hm, + null, "Signature-Version"); //$NON-NLS-1$ + } catch (IOException e) { + return; + } + + boolean createdBySigntool = false; + String createdByValue = attributes.getValue("Created-By"); //$NON-NLS-1$ + if (createdByValue != null) { + createdBySigntool = createdByValue.indexOf("signtool") != -1; //$NON-NLS-1$ + } + + // Use .SF to verify the mainAttributes of the manifest + // If there is no -Digest-Manifest-Main-Attributes entry in .SF + // file, such as those created before java 1.5, then we ignore + // such verification. + // FIXME: The meaning of createdBySigntool + if (mainAttributesChunk != null && !createdBySigntool) { + String digestAttribute = "-Digest-Manifest-Main-Attributes"; //$NON-NLS-1$ + if (!verify(attributes, digestAttribute, mainAttributesChunk, + false, true)) { + /* [MSG "archive.30", "{0} failed verification of {1}"] */ + throw new SecurityException( + Messages.getString("archive.30", jarName, signatureFile)); //$NON-NLS-1$ + } + } + + byte[] manifest = metaEntries.get(JarFile.MANIFEST_NAME); + if (manifest == null) { + return; + } + // Use .SF to verify the whole manifest + String digestAttribute = createdBySigntool ? "-Digest" //$NON-NLS-1$ + : "-Digest-Manifest"; //$NON-NLS-1$ + if (!verify(attributes, digestAttribute, manifest, false, false)) { + Iterator<Map.Entry<String, Attributes>> it = hm.entrySet() + .iterator(); + while (it.hasNext()) { + Map.Entry<String, Attributes> entry = it.next(); + byte[] chunk = man.getChunk(entry.getKey()); + if (chunk == null) { + return; + } + if (!verify(entry.getValue(), "-Digest", chunk, //$NON-NLS-1$ + createdBySigntool, false)) { + /* [MSG "archive.31", "{0} has invalid digest for {1} in {2}"] */ + throw new SecurityException( + Messages.getString("archive.31", //$NON-NLS-1$ + new Object[] { signatureFile, entry.getKey(), jarName })); + } + } + } + metaEntries.put(signatureFile, null); + signatures.put(signatureFile, hm); + } + + /** + * Associate this verifier with the specified {@link Manifest} object. + * + * @param mf + * a {@code java.util.jar.Manifest} object. + */ + void setManifest(Manifest mf) { + man = mf; + } + + /** + * Verifies that the digests stored in the manifest match the decrypted + * digests from the .SF file. This indicates the validity of the signing, + * not the integrity of the file, as it's digest must be calculated and + * verified when its contents are read. + * + * @param entry + * the {@link VerifierEntry} associated with the specified + * {@code zipEntry}. + * @param zipEntry + * an entry in the JAR file + * @throws SecurityException + * if the digest value stored in the manifest does <i>not</i> + * agree with the decrypted digest as recovered from the + * {@code .SF} file. + * @see #initEntry(String) + */ + void verifySignatures(VerifierEntry entry, ZipEntry zipEntry) { + byte[] digest = entry.digest.digest(); + if (!MessageDigest.isEqual(digest, Base64.decode(entry.hash))) { + /* [MSG "archive.31", "{0} has invalid digest for {1} in {2}"] */ + throw new SecurityException(Messages.getString("archive.31", new Object[] { //$NON-NLS-1$ + JarFile.MANIFEST_NAME, zipEntry.getName(), jarName })); + } + verifiedEntries.put(zipEntry.getName(), entry.certificates); + } + + /** + * Returns a {@code boolean} indication of whether or not the + * associated JAR file is signed. + * + * @return {@code true} if the JAR is signed, {@code false} + * otherwise. + */ + boolean isSignedJar() { + return certificates.size() > 0; + } + + private boolean verify(Attributes attributes, String entry, byte[] data, + boolean ignoreSecondEndline, boolean ignorable) { + String algorithms = attributes.getValue("Digest-Algorithms"); //$NON-NLS-1$ + if (algorithms == null) { + algorithms = "SHA SHA1"; //$NON-NLS-1$ + } + StringTokenizer tokens = new StringTokenizer(algorithms); + while (tokens.hasMoreTokens()) { + String algorithm = tokens.nextToken(); + String hash = attributes.getValue(algorithm + entry); + if (hash == null) { + continue; + } + + MessageDigest md; + try { + // BEGIN android-changed + md = OpenSSLMessageDigestJDK.getInstance(algorithm); + // END android-changed + } catch (NoSuchAlgorithmException e) { + continue; + } + if (ignoreSecondEndline && data[data.length - 1] == '\n' + && data[data.length - 2] == '\n') { + md.update(data, 0, data.length - 1); + } else { + md.update(data, 0, data.length); + } + byte[] b = md.digest(); + byte[] hashBytes; + try { + hashBytes = hash.getBytes("ISO8859_1"); //$NON-NLS-1$ + } catch (UnsupportedEncodingException e) { + throw new RuntimeException(e.toString()); + } + return MessageDigest.isEqual(b, Base64.decode(hashBytes)); + } + return ignorable; + } + + /** + * Returns all of the {@link java.security.cert.Certificate} instances that + * were used to verify the signature on the JAR entry called + * {@code name}. + * + * @param name + * the name of a JAR entry. + * @return an array of {@link java.security.cert.Certificate}. + */ + Certificate[] getCertificates(String name) { + Certificate[] verifiedCerts = verifiedEntries.get(name); + if (verifiedCerts == null) { + return null; + } + return verifiedCerts.clone(); + } + + /** + * Remove all entries from the internal collection of data held about each + * JAR entry in the {@code META-INF} directory. + * + * @see #addMetaEntry(String, byte[]) + */ + void removeMetaEntries() { + metaEntries = null; + } + + /** + * Returns a {@code Vector} of all of the + * {@link java.security.cert.Certificate}s that are associated with the + * signing of the named signature file. + * + * @param signatureFileName + * the name of a signature file. + * @param certificates + * a {@code Map} of all of the certificate chains discovered so + * far while attempting to verify the JAR that contains the + * signature file {@code signatureFileName}. This object is + * previously set in the course of one or more calls to + * {@link #verifyJarSignatureFile(String, String, String, Map, Map)} + * where it was passed as the last argument. + * @return all of the {@code Certificate} entries for the signer of the JAR + * whose actions led to the creation of the named signature file. + */ + public static Vector<Certificate> getSignerCertificates( + String signatureFileName, Map<String, Certificate[]> certificates) { + Vector<Certificate> result = new Vector<Certificate>(); + Certificate[] certChain = certificates.get(signatureFileName); + if (certChain != null) { + for (Certificate element : certChain) { + result.add(element); + } + } + return result; + } +} diff --git a/archive/src/main/java/java/util/jar/Manifest.java b/archive/src/main/java/java/util/jar/Manifest.java new file mode 100644 index 0000000..3b0d89a --- /dev/null +++ b/archive/src/main/java/java/util/jar/Manifest.java @@ -0,0 +1,344 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.jar; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.CharBuffer; +import java.nio.charset.Charset; +import java.security.AccessController; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.harmony.luni.util.PriviAction; + +/** + * The {@code Manifest} class is used to obtain attribute information for a + * {@code JarFile} and its entries. + * + * @since Android 1.0 + */ +public class Manifest implements Cloneable { + private static final int LINE_LENGTH_LIMIT = 70; + + private static final byte[] LINE_SEPARATOR = new byte[] { '\r', '\n' }; + + private static final Attributes.Name NAME_ATTRIBUTE = new Attributes.Name("Name"); //$NON-NLS-1$ + + private Attributes mainAttributes = new Attributes(); + + private HashMap<String, Attributes> entryAttributes = new HashMap<String, Attributes>(); + + private HashMap<String, byte[]> chunks; + + /** + * The data chunk of main attributes in the manifest is needed in + * verification. + */ + private byte[] mainAttributesChunk; + + /** + * Creates a new {@code Manifest} instance. + * + * @since Android 1.0 + */ + public Manifest() { + super(); + } + + /** + * Creates a new {@code Manifest} instance using the attributes obtained + * from the input stream. + * + * @param is + * {@code InputStream} to parse for attributes. + * @throws IOException + * if an IO error occurs while creating this {@code Manifest} + * @since Android 1.0 + */ + public Manifest(InputStream is) throws IOException { + super(); + read(is); + } + + /** + * Creates a new {@code Manifest} instance. The new instance will have the + * same attributes as those found in the parameter {@code Manifest}. + * + * @param man + * {@code Manifest} instance to obtain attributes from. + * @since Android 1.0 + */ + @SuppressWarnings("unchecked") + public Manifest(Manifest man) { + mainAttributes = (Attributes) man.mainAttributes.clone(); + entryAttributes = (HashMap<String, Attributes>) man.entryAttributes.clone(); + } + + Manifest(InputStream is, boolean readChunks) throws IOException { + if (readChunks) { + chunks = new HashMap<String, byte[]>(); + } + read(is); + } + + /** + * Resets the both the main attributes as well as the entry attributes + * associated with this {@code Manifest}. + * + * @since Android 1.0 + */ + public void clear() { + entryAttributes.clear(); + mainAttributes.clear(); + } + + /** + * Returns the {@code Attributes} associated with the parameter entry + * {@code name}. + * + * @param name + * the name of the entry to obtain {@code Attributes} from. + * @return the Attributes for the entry or {@code null} if the entry does + * not exist. + * @since Android 1.0 + */ + public Attributes getAttributes(String name) { + return getEntries().get(name); + } + + /** + * Returns a map containing the {@code Attributes} for each entry in the + * {@code Manifest}. + * + * @return the map of entry attributes. + * @since Android 1.0 + */ + public Map<String, Attributes> getEntries() { + return entryAttributes; + } + + /** + * Returns the main {@code Attributes} of the {@code JarFile}. + * + * @return main {@code Attributes} associated with the source {@code + * JarFile}. + * @since Android 1.0 + */ + public Attributes getMainAttributes() { + return mainAttributes; + } + + /** + * Creates a copy of this {@code Manifest}. The returned {@code Manifest} + * will equal the {@code Manifest} from which it was cloned. + * + * @return a copy of this instance. + * @since Android 1.0 + */ + @Override + public Object clone() { + return new Manifest(this); + } + + /** + * Writes out the attribute information of the receiver to the specified + * {@code OutputStream}. + * + * @param os + * The {@code OutputStream} to write to. + * @throws IOException + * If an error occurs writing the {@code Manifest}. + * @since Android 1.0 + */ + public void write(OutputStream os) throws IOException { + write(this, os); + } + + /** + * Constructs a new {@code Manifest} instance obtaining attribute + * information from the specified input stream. + * + * @param is + * The {@code InputStream} to read from. + * @throws IOException + * If an error occurs reading the {@code Manifest}. + * @since Android 1.0 + */ + public void read(InputStream is) throws IOException { + InitManifest initManifest = new InitManifest(is, mainAttributes, entryAttributes, + chunks, null); + mainAttributesChunk = initManifest.getMainAttributesChunk(); + } + + /** + * Returns the hash code for this instance. + * + * @return this {@code Manifest}'s hashCode. + * @since Android 1.0 + */ + @Override + public int hashCode() { + return mainAttributes.hashCode() ^ entryAttributes.hashCode(); + } + + /** + * Determines if the receiver is equal to the parameter object. Two {@code + * Manifest}s are equal if they have identical main attributes as well as + * identical entry attributes. + * + * @param o + * the object to compare against. + * @return {@code true} if the manifests are equal, {@code false} otherwise + * @since Android 1.0 + */ + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + if (o.getClass() != this.getClass()) { + return false; + } + if (!mainAttributes.equals(((Manifest) o).mainAttributes)) { + return false; + } + return entryAttributes.equals(((Manifest) o).entryAttributes); + } + + byte[] getChunk(String name) { + return chunks.get(name); + } + + void removeChunks() { + chunks = null; + } + + byte[] getMainAttributesChunk() { + return mainAttributesChunk; + } + + /** + * Writes out the attribute information of the specified manifest to the + * specified {@code OutputStream} + * + * @param manifest + * the manifest to write out. + * @param out + * The {@code OutputStream} to write to. + * @throws IOException + * If an error occurs writing the {@code Manifest}. + * @since Android 1.0 + */ + static void write(Manifest manifest, OutputStream out) throws IOException { + Charset charset = null; + String encoding = AccessController.doPrivileged(new PriviAction<String>( + "manifest.write.encoding")); //$NON-NLS-1$ + if (encoding != null) { + if (encoding.length() == 0) { + encoding = "UTF8"; //$NON-NLS-1$ + } + charset = Charset.forName(encoding); + } + String version = manifest.mainAttributes.getValue(Attributes.Name.MANIFEST_VERSION); + if (version != null) { + writeEntry(out, charset, Attributes.Name.MANIFEST_VERSION, version); + Iterator<?> entries = manifest.mainAttributes.keySet().iterator(); + while (entries.hasNext()) { + Attributes.Name name = (Attributes.Name) entries.next(); + if (!name.equals(Attributes.Name.MANIFEST_VERSION)) { + writeEntry(out, charset, name, manifest.mainAttributes.getValue(name)); + } + } + } + out.write(LINE_SEPARATOR); + Iterator<String> i = manifest.entryAttributes.keySet().iterator(); + while (i.hasNext()) { + String key = i.next(); + writeEntry(out, charset, NAME_ATTRIBUTE, key); + Attributes attrib = manifest.entryAttributes.get(key); + Iterator<?> entries = attrib.keySet().iterator(); + while (entries.hasNext()) { + Attributes.Name name = (Attributes.Name) entries.next(); + writeEntry(out, charset, name, attrib.getValue(name)); + } + out.write(LINE_SEPARATOR); + } + } + + private static void writeEntry(OutputStream os, Charset charset, Attributes.Name name, + String value) throws IOException { + int offset = 0; + int limit = LINE_LENGTH_LIMIT; + byte[] out = (name.toString() + ": ").getBytes("ISO8859_1"); //$NON-NLS-1$ //$NON-NLS-2$ + if (out.length > limit) { + while (out.length - offset >= limit) { + int len = out.length - offset; + if (len > limit) { + len = limit; + } + if (offset > 0) { + os.write(' '); + } + os.write(out, offset, len); + os.write(LINE_SEPARATOR); + offset += len; + limit = LINE_LENGTH_LIMIT - 1; + } + } + int size = out.length - offset; + final byte[] outBuf = new byte[LINE_LENGTH_LIMIT]; + System.arraycopy(out, offset, outBuf, 0, size); + for (int i = 0; i < value.length(); i++) { + char[] oneChar = new char[1]; + oneChar[0] = value.charAt(i); + byte[] buf; + if (oneChar[0] < 128 || charset == null) { + byte[] oneByte = new byte[1]; + oneByte[0] = (byte) oneChar[0]; + buf = oneByte; + } else { + buf = charset.encode(CharBuffer.wrap(oneChar, 0, 1)).array(); + } + if (size + buf.length > limit) { + if (limit != LINE_LENGTH_LIMIT) { + os.write(' '); + } + os.write(outBuf, 0, size); + os.write(LINE_SEPARATOR); + limit = LINE_LENGTH_LIMIT - 1; + size = 0; + } + if (buf.length == 1) { + outBuf[size] = buf[0]; + } else { + System.arraycopy(buf, 0, outBuf, size, buf.length); + } + size += buf.length; + } + if (size > 0) { + if (limit != LINE_LENGTH_LIMIT) { + os.write(' '); + } + os.write(outBuf, 0, size); + os.write(LINE_SEPARATOR); + } + } +} diff --git a/archive/src/main/java/java/util/jar/Pack200.java b/archive/src/main/java/java/util/jar/Pack200.java new file mode 100644 index 0000000..e0689e0 --- /dev/null +++ b/archive/src/main/java/java/util/jar/Pack200.java @@ -0,0 +1,398 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package java.util.jar; + +import java.beans.PropertyChangeListener; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.SortedMap; + +/** + * Class factory for {@link Pack200.Packer} and {@link Pack200.Unpacker}. + * + * @since Android 1.0 + */ +public abstract class Pack200 { + + private static final String SYSTEM_PROPERTY_PACKER = "java.util.jar.Pack200.Packer"; //$NON-NLS-1$ + + private static final String SYSTEM_PROPERTY_UNPACKER = "java.util.jar.Pack200.Unpacker"; //$NON-NLS-1$ + + /** + * Prevent this class from being instantiated. + */ + private Pack200(){ + //do nothing + } + + /** + * Returns a new instance of a packer engine. + * <p> + * The implementation of the packer engine is defined by the system property + * {@code 'java.util.jar.Pack200.Packer'}. If this system property is + * defined an instance of the specified class is returned, otherwise the + * system's default implementation is returned. + * </p> + * + * @return an instance of {@code Packer} + * @since Android 1.0 + */ + public static Pack200.Packer newPacker() { + return (Packer) AccessController + .doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + String className = System + .getProperty(SYSTEM_PROPERTY_PACKER, + "org.apache.harmony.archive.internal.pack200.Pack200PackerAdapter"); //$NON-NLS-1$ + try { + // TODO Not sure if this will cause problems with + // loading the packer + return ClassLoader.getSystemClassLoader() + .loadClass(className).newInstance(); + } catch (Exception e) { + throw new Error("Can't load class " + className, e); + } + } + }); + + } + + /** + * Returns a new instance of a unpacker engine. + * <p> + * The implementation of the unpacker engine is defined by the system + * property {@code 'java.util.jar.Pack200.Unpacker'}. If this system + * property is defined an instance of the specified class is returned, + * otherwise the system's default implementation is returned. + * </p> + * + * @return a instance of {@code Unpacker}. + * @since Android 1.0 + */ + public static Pack200.Unpacker newUnpacker() { + return (Unpacker) AccessController + .doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + String className = System + .getProperty(SYSTEM_PROPERTY_UNPACKER, + "org.apache.harmony.archive.internal.pack200.Pack200UnpackerAdapter");//$NON-NLS-1$ + try { + return ClassLoader.getSystemClassLoader() + .loadClass(className).newInstance(); + } catch (Exception e) { + throw new Error("Can't load class " + className, e); + } + } + }); + } + + /** + * The interface defining the API for converting a JAR file to an output + * stream in the Pack200 format. + * + * @since Android 1.0 + */ + public static interface Packer { + + /** + * the format of a class attribute name. + * + * @since Android 1.0 + */ + static final String CLASS_ATTRIBUTE_PFX = "pack.class.attribute."; //$NON-NLS-1$ + + /** + * the format of a code attribute name. + * + * @since Android 1.0 + */ + static final String CODE_ATTRIBUTE_PFX = "pack.code.attribute."; //$NON-NLS-1$ + + /** + * the deflation hint to set in the output archive. + * + * @since Android 1.0 + */ + static final String DEFLATE_HINT = "pack.deflate.hint";//$NON-NLS-1$ + + /** + * the indicated amount of effort to use in compressing the archive. + * + * @since Android 1.0 + */ + static final String EFFORT = "pack.effort";//$NON-NLS-1$ + + /** + * a String representation for {@code error}. + * + * @since Android 1.0 + */ + static final String ERROR = "error";//$NON-NLS-1$ + + /** + * a String representation of {@code false}. + * + * @since Android 1.0 + */ + static final String FALSE = "false";//$NON-NLS-1$ + + /** + * the format of a field attribute name. + * + * @since Android 1.0 + */ + static final String FIELD_ATTRIBUTE_PFX = "pack.field.attribute.";//$NON-NLS-1$ + + /** + * a String representation for {@code keep}. + * + * @since Android 1.0 + */ + static final String KEEP = "keep";//$NON-NLS-1$ + + /** + * decide if all elements shall transmit in their original order. + * + * @since Android 1.0 + */ + static final String KEEP_FILE_ORDER = "pack.keep.file.order";//$NON-NLS-1$ + + /** + * a String representation for {@code latest}. + * + * @since Android 1.0 + */ + static final String LATEST = "latest";//$NON-NLS-1$ + + /** + * the format of a method attribute name. + * + * @since Android 1.0 + */ + static final String METHOD_ATTRIBUTE_PFX = "pack.method.attribute.";//$NON-NLS-1$ + + /** + * if it shall attempt to determine the latest modification time if this + * is set to {@code LATEST}. + * + * @since Android 1.0 + */ + static final String MODIFICATION_TIME = "pack.modification.time";//$NON-NLS-1$ + + /** + * a String representation of {@code pass}. + * + * @since Android 1.0 + */ + static final String PASS = "pass";//$NON-NLS-1$ + + /** + * the file that will not be compressed. + * + * @since Android 1.0 + */ + static final String PASS_FILE_PFX = "pack.pass.file.";//$NON-NLS-1$ + + /** + * packer progress as a percentage. + * + * @since Android 1.0 + */ + static final String PROGRESS = "pack.progress";//$NON-NLS-1$ + + /** + * The number of bytes of each archive segment. + * + * @since Android 1.0 + */ + static final String SEGMENT_LIMIT = "pack.segment.limit";//$NON-NLS-1$ + + /** + * a String representation of {@code strip}. + * + * @since Android 1.0 + */ + static final String STRIP = "strip";//$NON-NLS-1$ + + /** + * a String representation of {@code true}. + * + * @since Android 1.0 + */ + static final String TRUE = "true";//$NON-NLS-1$ + + /** + * the action to take if an unknown attribute is encountered. + * + * @since Android 1.0 + */ + static final String UNKNOWN_ATTRIBUTE = "pack.unknown.attribute";//$NON-NLS-1$ + + /** + * Returns a sorted map of the properties of this packer. + * + * @return the properties of the packer. + * @since Android 1.0 + */ + SortedMap<String, String> properties(); + + /** + * Pack the specified JAR file to the specified output stream. + * + * @param in + * JAR file to be compressed. + * @param out + * stream of compressed data. + * @throws IOException + * if I/O exception occurs. + * @since Android 1.0 + */ + void pack(JarFile in, OutputStream out) throws IOException; + + /** + * Pack the data from the specified jar input stream to the specified + * output stream. + * + * @param in + * stream of uncompressed JAR data. + * @param out + * stream of compressed data. + * @throws IOException + * if I/O exception occurs. + * @since Android 1.0 + */ + void pack(JarInputStream in, OutputStream out) throws IOException; + + /** + * add a listener for PropertyChange events + * + * @param listener + * the listener to listen if PropertyChange events occurs + */ + void addPropertyChangeListener(PropertyChangeListener listener); + + /** + * remove a listener + * + * @param listener + * listener to remove + */ + void removePropertyChangeListener(PropertyChangeListener listener); + } + + /** + * The interface defining the API for converting a packed stream in the + * Pack200 format to a JAR file. + * + * @since Android 1.0 + */ + public static interface Unpacker { + + /** + * The String indicating if the unpacker should ignore all transmitted + * values,can be replaced by either {@code true} or {@code false}. + * + * @since Android 1.0 + */ + static final String DEFLATE_HINT = "unpack.deflate.hint";//$NON-NLS-1$ + + /** + * a String representation of {@code false}. + * + * @since Android 1.0 + */ + static final String FALSE = "false";//$NON-NLS-1$ + + /** + * a String representation of {@code keep}. + * + * @since Android 1.0 + */ + static final String KEEP = "keep";//$NON-NLS-1$ + + /** + * the progress as a {@code percentage}. + * + * @since Android 1.0 + */ + static final String PROGRESS = "unpack.progress";//$NON-NLS-1$ + + /** + * a String representation of {@code true}. + * + * @since Android 1.0 + */ + static final String TRUE = "true";//$NON-NLS-1$ + + /** + * Returns a sorted map of the properties of this unpacker. + * + * @return the properties of unpacker. + * @since Android 1.0 + */ + SortedMap<String, String> properties(); + + /** + * Unpack the specified stream to the specified JAR output stream. + * + * @param in + * stream to uncompressed. + * @param out + * JAR output stream of uncompressed data. + * @throws IOException + * if I/O exception occurs. + * @since Android 1.0 + */ + void unpack(InputStream in, JarOutputStream out) throws IOException; + + /** + * Unpack the contents of the specified {@code File} to the specified + * JAR output stream. + * + * @param in + * file to be uncompressed. + * @param out + * JAR output stream of uncompressed data. + * @throws IOException + * if I/O exception occurs. + * @since Android 1.0 + */ + void unpack(File in, JarOutputStream out) throws IOException; + + /** + * add a listener for {@code PropertyChange} events. + * + * @param listener + * the listener to listen if {@code PropertyChange} events + * occurs. + */ + void addPropertyChangeListener(PropertyChangeListener listener); + + /** + * remove a listener. + * + * @param listener + * listener to remove. + */ + void removePropertyChangeListener(PropertyChangeListener listener); + } + +} diff --git a/archive/src/main/java/java/util/jar/package.html b/archive/src/main/java/java/util/jar/package.html new file mode 100644 index 0000000..35eabe7 --- /dev/null +++ b/archive/src/main/java/java/util/jar/package.html @@ -0,0 +1,12 @@ +<html> + <body> + <p> + The java.jar package gives access to reading and writing a Java archive, + or JAR, files. These are + actually ZIP files with the possibility to add meta-information in the + form of a MANIFEST file. This manifest can also be used + to sign a JAR file. + </p> + @since Android 1.0 + </body> +</html> diff --git a/archive/src/main/java/java/util/zip/Adler32.java b/archive/src/main/java/java/util/zip/Adler32.java new file mode 100644 index 0000000..8eaa18e --- /dev/null +++ b/archive/src/main/java/java/util/zip/Adler32.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +/** + * The Adler-32 class is used to compute the {@code Adler32} checksum from a set + * of data. Compared to the CRC-32 algorithm it trades reliabilty for speed. + * Refer to RFC 1950 for the specification. + * + * @see CRC32 + * @since Android 1.0 + */ +public class Adler32 implements java.util.zip.Checksum { + + private long adler = 1; + + /** + * Returns the {@code Adler32} checksum for all input received. + * + * @return The checksum for this instance. + * @since Android 1.0 + */ + public long getValue() { + return adler; + } + + /** + * Reset this instance to its initial checksum. + * + * @since Android 1.0 + */ + public void reset() { + adler = 1; + } + + /** + * Update this {@code Adler32} checksum with the single byte provided as + * argument. + * + * @param i + * the byte to update checksum with. + * @since Android 1.0 + */ + public void update(int i) { + adler = updateByteImpl(i, adler); + } + + /** + * Update this {@code Adler32} checksum using the contents of {@code buf}. + * + * @param buf + * bytes to update checksum with. + * @since Android 1.0 + */ + public void update(byte[] buf) { + update(buf, 0, buf.length); + } + + /** + * Update this {@code Adler32} checksum with the contents of {@code buf}, + * starting from the offset provided and reading n bytes of data. + * + * @param buf + * buffer to obtain data from. + * @param off + * offset in {@code buf} to start reading from. + * @param nbytes + * number of bytes from {@code buf} to use. + * @throws ArrayIndexOutOfBoundsException + * if {@code offset > buf.length} or {@code nbytes} is negative + * or {@code offset + nbytes > buf.length}. + * @since Android 1.0 + */ + public void update(byte[] buf, int off, int nbytes) { + // avoid int overflow, check null buf + if (off <= buf.length && nbytes >= 0 && off >= 0 + && buf.length - off >= nbytes) { + adler = updateImpl(buf, off, nbytes, adler); + } else { + throw new ArrayIndexOutOfBoundsException(); + } + } + + private native long updateImpl(byte[] buf, int off, int nbytes, long adler1); + + private native long updateByteImpl(int val, long adler1); +} diff --git a/archive/src/main/java/java/util/zip/CRC32.java b/archive/src/main/java/java/util/zip/CRC32.java new file mode 100644 index 0000000..36dc376 --- /dev/null +++ b/archive/src/main/java/java/util/zip/CRC32.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +/** + * The CRC32 class is used to compute a CRC32 checksum from data provided as + * input value. + * + * @since Android 1.0 + */ +public class CRC32 implements java.util.zip.Checksum { + + private long crc = 0L; + + long tbytes = 0L; + + /** + * Returns the CRC32 checksum for all input received. + * + * @return The checksum for this instance. + * @since Android 1.0 + */ + public long getValue() { + return crc; + } + + /** + * Resets the CRC32 checksum to it initial state. + * + * @since Android 1.0 + */ + public void reset() { + tbytes = crc = 0; + + } + + /** + * Updates this checksum with the byte value provided as integer. + * + * @param val + * represents the byte to update the checksum. + * @since Android 1.0 + */ + public void update(int val) { + crc = updateByteImpl((byte) val, crc); + } + + /** + * Updates this checksum with the bytes contained in buffer {@code buf}. + * + * @param buf + * the buffer holding the data to update the checksum with. + * @since Android 1.0 + */ + public void update(byte[] buf) { + update(buf, 0, buf.length); + } + + /** + * Updates this checksum with n bytes of data obtained from buffer {@code + * buf}, starting at offset {@code off}. + * + * @param buf + * the buffer to update the checksum. + * @param off + * the offset in {@code buf} to obtain data from. + * @param nbytes + * the number of bytes to read from {@code buf}. + * @since Android 1.0 + */ + public void update(byte[] buf, int off, int nbytes) { + // avoid int overflow, check null buf + if (off <= buf.length && nbytes >= 0 && off >= 0 + && buf.length - off >= nbytes) { + tbytes += nbytes; + crc = updateImpl(buf, off, nbytes, crc); + } else { + throw new ArrayIndexOutOfBoundsException(); + } + } + + private native long updateImpl(byte[] buf, int off, int nbytes, long crc1); + + private native long updateByteImpl(byte val, long crc1); +} diff --git a/archive/src/main/java/java/util/zip/CheckedInputStream.java b/archive/src/main/java/java/util/zip/CheckedInputStream.java new file mode 100644 index 0000000..659125a --- /dev/null +++ b/archive/src/main/java/java/util/zip/CheckedInputStream.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +import java.io.IOException; +import java.io.InputStream; + +/** + * The {@code CheckedInputStream} class is used to maintain a checksum at the + * same time as the data, on which the checksum is computed, is read from a + * stream. The purpose of this checksum is to establish data integrity, + * comparing the computed checksum against a published checksum value. + * + * @since Android 1.0 + */ +public class CheckedInputStream extends java.io.FilterInputStream { + + private final Checksum check; + + /** + * Constructs a new {@code CheckedInputStream} on {@code InputStream} + * {@code is}. The checksum will be calculated using the algorithm + * implemented by {@code csum}. + * + * @param is + * the input stream to calculate checksum from. + * @param csum + * an entity implementing the checksum algorithm. + * @since Android 1.0 + */ + public CheckedInputStream(InputStream is, Checksum csum) { + super(is); + check = csum; + } + + /** + * Reads one byte of data from the underlying input stream and updates the + * checksum with the byte data. + * + * @return {@code -1} at the end of the stream, a single byte value + * otherwise. + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + @Override + public int read() throws IOException { + int x = in.read(); + if (x != -1) { + check.update(x); + } + return x; + } + + /** + * Reads up to n bytes of data from the underlying input stream, storing it + * into {@code buf}, starting at offset {@code off}. The checksum is + * updated with the bytes read. + * + * @param buf + * the byte array in which to store the bytes read. + * @param off + * the initial position in {@code buf} to store the bytes read + * from this stream. + * @param nbytes + * the maximum number of bytes to store in {@code buf}. + * @return the number of bytes actually read or {@code -1} if arrived at the + * end of the filtered stream while reading the data. + * @throws IOException + * if this stream is closed or some I/O error occurs. + * @since Android 1.0 + */ + @Override + public int read(byte[] buf, int off, int nbytes) throws IOException { + int x = in.read(buf, off, nbytes); + if (x != -1) { + check.update(buf, off, x); + } + return x; + } + + /** + * Returns the checksum calculated on the stream read so far. + * + * @return the updated checksum. + * @since Android 1.0 + */ + public Checksum getChecksum() { + return check; + } + + /** + * Skip up to n bytes of data on the underlying input stream. Any skipped + * bytes are added to the running checksum value. + * + * @param nbytes + * the number of bytes to skip. + * @throws IOException + * if this stream is closed or another I/O error occurs. + * @return the number of bytes skipped. + * @since Android 1.0 + */ + @Override + public long skip(long nbytes) throws IOException { + if (nbytes < 1) { + return 0; + } + long skipped = 0; + byte[] b = new byte[2048]; + int x, v; + while (skipped != nbytes) { + x = in.read(b, 0, + (v = (int) (nbytes - skipped)) > b.length ? b.length : v); + if (x == -1) { + return skipped; + } + check.update(b, 0, x); + skipped += x; + } + return skipped; + } +} diff --git a/archive/src/main/java/java/util/zip/CheckedOutputStream.java b/archive/src/main/java/java/util/zip/CheckedOutputStream.java new file mode 100644 index 0000000..9699492 --- /dev/null +++ b/archive/src/main/java/java/util/zip/CheckedOutputStream.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +import java.io.IOException; +import java.io.OutputStream; + +/** + * The {@code CheckedOutputStream} class is used to maintain a running checksum + * of all data written to a stream. The purpose of this checksum is to establish + * data integrity, by publishing the checksum to other parties wanting to read + * the non corrupted data. + * + * @since Android 1.0 + */ +public class CheckedOutputStream extends java.io.FilterOutputStream { + + private final Checksum check; + + /** + * Constructs a new {@code CheckedOutputStream} on {@code OutputStream} + * {@code os}. The checksum is calculated using the algorithm implemented + * by {@code csum}. + * + * @param os + * the output stream to calculate checksum for. + * @param cs + * an entity implementing the checksum algorithm. + * @since Android 1.0 + */ + public CheckedOutputStream(OutputStream os, Checksum cs) { + super(os); + check = cs; + } + + /** + * Returns the checksum calculated on the stream read so far. + * + * @return the updated checksum. + * @since Android 1.0 + */ + public Checksum getChecksum() { + return check; + } + + /** + * Writes the specified byte to the underlying stream. The checksum is + * updated with {@code val}. + * + * @param val + * the data value to written to the output stream. + * @throws IOException + * if an IO error has occurred. + * @since Android 1.0 + */ + @Override + public void write(int val) throws IOException { + out.write(val); + check.update(val); + } + + /** + * Writes n bytes of data from {@code buf} starting at offset {@code off} to + * the underlying stream. The checksum is updated with the bytes written. + * + * @param buf + * data written to the output stream. + * @param off + * the offset to start reading the data from {@code buf} written + * to the output stream. + * @param nbytes + * number of bytes to write to the output stream. + * @throws IOException + * if an IO error has occurred. + * @since Android 1.0 + */ + @Override + public void write(byte[] buf, int off, int nbytes) throws IOException { + out.write(buf, off, nbytes); + check.update(buf, off, nbytes); + } +} diff --git a/archive/src/main/java/java/util/zip/Checksum.java b/archive/src/main/java/java/util/zip/Checksum.java new file mode 100644 index 0000000..0405c08 --- /dev/null +++ b/archive/src/main/java/java/util/zip/Checksum.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + +/** + * Holds information about a checksum which was computed with the methods + * implementing a checksum algorithm. + * + * @since Android 1.0 + */ +public interface Checksum { + + /** + * Returns the current calculated checksum value. + * + * @return the checksum. + * @since Android 1.0 + */ + public long getValue(); + + /** + * Resets the checksum value applied before beginning calculations on a new + * stream of data. + * + * @since Android 1.0 + */ + public void reset(); + + /** + * Updates the checksum value with the given byte. + * + * @param val + * the byte to update the checksum with. + * @since Android 1.0 + */ + public void update(int val); + + /** + * Updates the checksum with the given bytes. + * + * @param buf + * the byte array from which to read the bytes. + * @param off + * the initial position in {@code buf} to read the bytes from. + * @param nbytes + * the number of bytes to read from {@code buf}. + * @since Android 1.0 + */ + public void update(byte[] buf, int off, int nbytes); +} diff --git a/archive/src/main/java/java/util/zip/DataFormatException.java b/archive/src/main/java/java/util/zip/DataFormatException.java new file mode 100644 index 0000000..9ffe2ab --- /dev/null +++ b/archive/src/main/java/java/util/zip/DataFormatException.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + +/** + * {@code DataFormatException} is used to indicate an error in the format of a + * particular data stream which is to be uncompressed. + * + * @since Android 1.0 + */ +public class DataFormatException extends Exception { + + private static final long serialVersionUID = 2219632870893641452L; + + /** + * Constructs a new {@code DataFormatException} instance. + * + * @since Android 1.0 + */ + public DataFormatException() { + super(); + } + + /** + * Constructs a new {@code DataFormatException} instance with the specified + * message. + * + * @param detailMessage + * the detail message for the exception. + * @since Android 1.0 + */ + public DataFormatException(String detailMessage) { + super(detailMessage); + } +} diff --git a/archive/src/main/java/java/util/zip/Deflater.java b/archive/src/main/java/java/util/zip/Deflater.java new file mode 100644 index 0000000..f91f1ca --- /dev/null +++ b/archive/src/main/java/java/util/zip/Deflater.java @@ -0,0 +1,533 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + +/** + * This class compresses data using the <i>DEFLATE</i> algorithm (see <a + * href="http://www.gzip.org/algorithm.txt">specification</a>). + * <p> + * Basically this class is part of the API to the stream based ZLIB compression + * library and is used as such by {@code DeflaterOutputStream} and its + * descendants. + * </p> + * <p> + * The typical usage of a {@code Deflater} instance outside this package + * consists of a specific call to one of its constructors before being passed to + * an instance of {@code DeflaterOutputStream}. + * </p> + * + * @see DeflaterOutputStream + * @see Inflater + * @since Android 1.0 + */ +public class Deflater { + + /** + * Upper bound for the compression level range. + * + * @since Android 1.0 + */ + public static final int BEST_COMPRESSION = 9; + + /** + * Lower bound for compression level range. + * + * @since Android 1.0 + */ + public static final int BEST_SPEED = 1; + + /** + * Usage of the default compression level. + * + * @since Android 1.0 + */ + public static final int DEFAULT_COMPRESSION = -1; + + /** + * Default value for compression strategy. + * + * @since Android 1.0 + */ + public static final int DEFAULT_STRATEGY = 0; + + /** + * Default value for compression method. + * + * @since Android 1.0 + */ + public static final int DEFLATED = 8; + + /** + * Possible value for compression strategy. + * + * @since Android 1.0 + */ + public static final int FILTERED = 1; + + /** + * Possible value for compression strategy. + * + * @since Android 1.0 + */ + public static final int HUFFMAN_ONLY = 2; + + /** + * Possible value for compression level. + * + * @since Android 1.0 + */ + public static final int NO_COMPRESSION = 0; + + private static final int Z_NO_FLUSH = 0; + + private static final int Z_FINISH = 4; + + // Fill in the JNI id caches + private static native void oneTimeInitialization(); + + // A stub buffer used when deflate() called while inputBuffer has not been set. + private static final byte[] STUB_INPUT_BUFFER = new byte[0]; + + static { + oneTimeInitialization(); + } + + private int flushParm = Z_NO_FLUSH; + + private boolean finished; + + private int compressLevel = DEFAULT_COMPRESSION; + + private int strategy = DEFAULT_STRATEGY; + + private long streamHandle = -1; + + private byte[] inputBuffer; + + private int inRead; + + private int inLength; + + /** + * Constructs a new {@code Deflater} instance with default compression + * level. The strategy can be specified with {@link #setStrategy}, only. A + * header is added to the output by default; use constructor {@code + * Deflater(level, boolean)} if you need to omit the header. + * + * @since Android 1.0 + */ + public Deflater() { + this(DEFAULT_COMPRESSION, false); + } + + /** + * Constructs a new {@code Deflater} instance with a specific compression + * level. The strategy can be specified with {@code setStrategy}, only. A + * header is added to the output by default; use + * {@code Deflater(level, boolean)} if you need to omit the header. + * + * @param level + * the compression level in the range between 0 and 9. + * @since Android 1.0 + */ + public Deflater(int level) { + this(level, false); + } + + /** + * Constructs a new {@code Deflater} instance with a specific compression + * level. If noHeader is passed as true no ZLib header is added to the + * output. In a ZIP archive every entry (compressed file) comes with such a + * header. The strategy can be specified with the setStrategy method, only. + * + * @param level + * the compression level in the range between 0 and 9. + * @param noHeader + * {@code true} indicates that no ZLIB header should be written. + * @since Android 1.0 + */ + public Deflater(int level, boolean noHeader) { + super(); + if (level < DEFAULT_COMPRESSION || level > BEST_COMPRESSION) { + throw new IllegalArgumentException(); + } + compressLevel = level; + streamHandle = createStream(compressLevel, strategy, noHeader); + } + + /** + * Deflates the data (previously passed to {@code setInput}) into the + * supplied buffer. + * + * @param buf + * buffer to write compressed data to. + * @return number of bytes of compressed data written to {@code buf}. + * @see #deflate(byte[], int, int) + * @since Android 1.0 + */ + public int deflate(byte[] buf) { + return deflate(buf, 0, buf.length); + } + + /** + * Deflates data (previously passed to {@code setInput}) into a specific + * region within the supplied buffer. + * + * @param buf + * the buffer to write compressed data to. + * @param off + * the offset within {@code buf} at which to start writing to. + * @param nbytes + * maximum number of bytes of compressed data to be written. + * @return the number of bytes of compressed data written to {@code buf}. + * @since Android 1.0 + */ + public synchronized int deflate(byte[] buf, int off, int nbytes) { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + // avoid int overflow, check null buf + if (off <= buf.length && nbytes >= 0 && off >= 0 + && buf.length - off >= nbytes) { + // put a stub buffer, no effect. + if (null == inputBuffer) { + setInput(STUB_INPUT_BUFFER); + } + return deflateImpl(buf, off, nbytes, streamHandle, flushParm); + } + throw new ArrayIndexOutOfBoundsException(); + } + + private synchronized native int deflateImpl(byte[] buf, int off, + int nbytes, long handle, int flushParm1); + + private synchronized native void endImpl(long handle); + + /** + * Frees all resources held onto by this deflating algorithm. Any unused + * input or output is discarded. While this method is used by {@code + * finalize()}, it can be called explicitly in order to free native + * resources before the next GC cycle. After {@code end()} was called other + * methods will typically throw an {@code IllegalStateException}. + * + * @since Android 1.0 + */ + public synchronized void end() { + if (streamHandle != -1) { + endImpl(streamHandle); + inputBuffer = null; + streamHandle = -1; + } + } + + @Override + protected void finalize() { + end(); + } + + /** + * Indicates to the {@code Deflater} that all uncompressed input has been provided + * to it. + * + * @see #finished + * @since Android 1.0 + */ + public synchronized void finish() { + flushParm = Z_FINISH; + } + + /** + * Returns whether or not all provided data has been successfully + * compressed. + * + * @return true if all data has been compressed, false otherwise. + * @since Android 1.0 + */ + public synchronized boolean finished() { + return finished; + } + + /** + * Returns the Adler32 checksum of uncompressed data currently read. If a + * preset dictionary is used getAdler() will return the Adler32 checksum of + * the dictionary used. + * + * @return the Adler32 checksum of uncompressed data or preset dictionary if + * used. + * @see #setDictionary(byte[]) + * @see #setDictionary(byte[], int, int) + * @since Android 1.0 + */ + public synchronized int getAdler() { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + + return getAdlerImpl(streamHandle); + } + + private synchronized native int getAdlerImpl(long handle); + + /** + * Returns the total number of bytes of input consumed by the {@code Deflater}. + * + * @return number of bytes of input read. + * @since Android 1.0 + */ + public synchronized int getTotalIn() { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + + return (int)getTotalInImpl(streamHandle); + } + + private synchronized native long getTotalInImpl(long handle); + + /** + * Returns the total number of compressed bytes output by this {@code Deflater}. + * + * @return number of compressed bytes output. + * @since Android 1.0 + */ + public synchronized int getTotalOut() { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + + return (int)getTotalOutImpl(streamHandle); + } + + private synchronized native long getTotalOutImpl(long handle); + + /** + * Counterpart to setInput(). Indicates whether or not all bytes of + * uncompressed input have been consumed by the {@code Deflater}. If needsInput() + * returns true setInput() must be called before deflation can continue. If + * all bytes of uncompressed data have been provided to the {@code Deflater} + * finish() must be called to ensure the compressed data is output. + * + * @return {@code true} if input is required for deflation to continue, + * {@code false} otherwise. + * @see #finished() + * @see #setInput(byte[]) + * @see #setInput(byte[], int, int) + * @since Android 1.0 + */ + public synchronized boolean needsInput() { + if (inputBuffer == null) { + return true; + } + return inRead == inLength; + } + + /** + * Resets the {@code Deflater} to accept new input without affecting any + * previously made settings for the compression strategy or level. This + * operation <i>must</i> be called after {@code finished()} returns + * {@code true} if the {@code Deflater} is to be reused. + * + * @see #finished + * @since Android 1.0 + */ + public synchronized void reset() { + if (streamHandle == -1) { + throw new NullPointerException(); + } + + flushParm = Z_NO_FLUSH; + finished = false; + resetImpl(streamHandle); + inputBuffer = null; + } + + private synchronized native void resetImpl(long handle); + + /** + * Sets the dictionary to be used for compression by this {@code Deflater}. + * setDictionary() can only be called if this {@code Deflater} supports the writing + * of ZLIB headers. This is the default behaviour but can be overridden + * using {@code Deflater(int, boolean)}. + * + * @param buf + * the buffer containing the dictionary data bytes. + * @see Deflater#Deflater(int, boolean) + * @since Android 1.0 + */ + public void setDictionary(byte[] buf) { + setDictionary(buf, 0, buf.length); + } + + /** + * Sets the dictionary to be used for compression by this {@code Deflater}. + * setDictionary() can only be called if this {@code Deflater} supports the writing + * of ZLIB headers. This is the default behaviour but can be overridden + * using {@code Deflater(int, boolean)}. + * + * @param buf + * the buffer containing the dictionary data bytes. + * @param off + * the offset of the data. + * @param nbytes + * the length of the data. + * @see Deflater#Deflater(int, boolean) + * @since Android 1.0 + */ + public synchronized void setDictionary(byte[] buf, int off, int nbytes) { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + // avoid int overflow, check null buf + if (off <= buf.length && nbytes >= 0 && off >= 0 + && buf.length - off >= nbytes) { + setDictionaryImpl(buf, off, nbytes, streamHandle); + } else { + throw new ArrayIndexOutOfBoundsException(); + } + } + + private synchronized native void setDictionaryImpl(byte[] buf, int off, + int nbytes, long handle); + + /** + * Sets the input buffer the {@code Deflater} will use to extract uncompressed bytes + * for later compression. + * + * @param buf + * the buffer. + * @since Android 1.0 + */ + public void setInput(byte[] buf) { + setInput(buf, 0, buf.length); + } + + /** + * Sets the input buffer the {@code Deflater} will use to extract uncompressed bytes + * for later compression. Input will be taken from the buffer region + * starting at off and ending at nbytes - 1. + * + * @param buf + * the buffer containing the input data bytes. + * @param off + * the offset of the data. + * @param nbytes + * the length of the data. + * @since Android 1.0 + */ + public synchronized void setInput(byte[] buf, int off, int nbytes) { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + // avoid int overflow, check null buf + if (off <= buf.length && nbytes >= 0 && off >= 0 + && buf.length - off >= nbytes) { + inLength = nbytes; + inRead = 0; + if (inputBuffer == null) { + setLevelsImpl(compressLevel, strategy, streamHandle); + } + inputBuffer = buf; + setInputImpl(buf, off, nbytes, streamHandle); + } else { + throw new ArrayIndexOutOfBoundsException(); + } + } + + private synchronized native void setLevelsImpl(int level, int strategy, + long handle); + + private synchronized native void setInputImpl(byte[] buf, int off, + int nbytes, long handle); + + /** + * Sets the compression level to be used when compressing data. The + * compression level must be a value between 0 and 9. This value must be set + * prior to calling setInput(). + * + * @param level + * compression level to use + * @exception IllegalArgumentException + * If the compression level is invalid. + * @since Android 1.0 + */ + public synchronized void setLevel(int level) { + if (level < DEFAULT_COMPRESSION || level > BEST_COMPRESSION) { + throw new IllegalArgumentException(); + } + if (inputBuffer != null) { + throw new IllegalStateException(); + } + compressLevel = level; + } + + /** + * Sets the compression strategy to be used. The strategy must be one of + * FILTERED, HUFFMAN_ONLY or DEFAULT_STRATEGY.This value must be set prior + * to calling setInput(). + * + * @param strategy + * compression strategy to use + * @exception IllegalArgumentException + * If the strategy specified is not one of FILTERED, + * HUFFMAN_ONLY or DEFAULT_STRATEGY. + * @since Android 1.0 + */ + public synchronized void setStrategy(int strategy) { + if (strategy < DEFAULT_STRATEGY || strategy > HUFFMAN_ONLY) { + throw new IllegalArgumentException(); + } + if (inputBuffer != null) { + throw new IllegalStateException(); + } + this.strategy = strategy; + } + + /** + * Returns a long int of total number of bytes read by the {@code Deflater}. This + * method performs the same as {@code getTotalIn} except it returns a long value + * instead of an integer + * + * @return total number of bytes read by {@code Deflater}. + * @since Android 1.0 + */ + public synchronized long getBytesRead() { + // Throw NPE here + if (streamHandle == -1) { + throw new NullPointerException(); + } + return getTotalInImpl(streamHandle); + } + + /** + * Returns a long int of total number of bytes of read by the {@code Deflater}. This + * method performs the same as {@code getTotalOut} except it returns a long + * value instead of an integer + * + * @return bytes exactly write by {@code Deflater} + * @since Android 1.0 + */ + public synchronized long getBytesWritten() { + // Throw NPE here + if (streamHandle == -1) { + throw new NullPointerException(); + } + return getTotalOutImpl(streamHandle); + } + + private native long createStream(int level, int strategy1, boolean noHeader1); +} diff --git a/archive/src/main/java/java/util/zip/DeflaterOutputStream.java b/archive/src/main/java/java/util/zip/DeflaterOutputStream.java new file mode 100644 index 0000000..773e4c4 --- /dev/null +++ b/archive/src/main/java/java/util/zip/DeflaterOutputStream.java @@ -0,0 +1,208 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import org.apache.harmony.archive.internal.nls.Messages; + +/** + * This class provides an implementation of {@code FilterOutputStream} that + * compresses data using the <i>DEFLATE</i> algorithm. Basically it wraps the + * {@code Deflater} class and takes care of the buffering. + * + * @see Deflater + * @since Android 1.0 + */ +public class DeflaterOutputStream extends FilterOutputStream { + static final int BUF_SIZE = 512; + + /** + * The buffer for the data to be written to. + * + * @since Android 1.0 + */ + protected byte[] buf; + + /** + * The deflater used. + * + * @since Android 1.0 + */ + protected Deflater def; + + boolean done = false; + + /** + * This constructor lets you pass the {@code Deflater} specifying the + * compression algorithm. + * + * @param os + * is the {@code OutputStream} where to write the compressed data + * to. + * @param def + * is the specific {@code Deflater} that is used to compress + * data. + * @since Android 1.0 + */ + public DeflaterOutputStream(OutputStream os, Deflater def) { + this(os, def, BUF_SIZE); + } + + /** + * This is the most basic constructor. You only need to pass the {@code + * OutputStream} to which the compressed data shall be written to. The + * default settings for the {@code Deflater} and internal buffer are used. + * In particular the {@code Deflater} produces a ZLIB header in the output + * stream. + * + * @param os + * is the OutputStream where to write the compressed data to. + * @since Android 1.0 + */ + public DeflaterOutputStream(OutputStream os) { + this(os, new Deflater()); + } + + /** + * This constructor lets you specify both the compression algorithm as well + * as the internal buffer size to be used. + * + * @param os + * is the {@code OutputStream} where to write the compressed data + * to. + * @param def + * is the specific {@code Deflater} that will be used to compress + * data. + * @param bsize + * is the size to be used for the internal buffer. + * @since Android 1.0 + */ + public DeflaterOutputStream(OutputStream os, Deflater def, int bsize) { + super(os); + if (os == null || def == null) { + throw new NullPointerException(); + } + if (bsize <= 0) { + throw new IllegalArgumentException(); + } + this.def = def; + buf = new byte[bsize]; + } + + /** + * Compress the data in the input buffer and write it to the underlying + * stream. + * + * @throws IOException + * If an error occurs during deflation. + * @since Android 1.0 + */ + protected void deflate() throws IOException { + int x = 0; + do { + x = def.deflate(buf); + out.write(buf, 0, x); + } while (!def.needsInput()); + } + + /** + * Writes any unwritten compressed data to the underlying stream, the closes + * all underlying streams. This stream can no longer be used after close() + * has been called. + * + * @throws IOException + * If an error occurs while closing the data compression + * process. + * @since Android 1.0 + */ + @Override + public void close() throws IOException { + if (!def.finished()) { + finish(); + } + def.end(); + out.close(); + } + + /** + * Writes any unwritten data to the underlying stream. Does not close the + * stream. + * + * @throws IOException + * If an error occurs. + * @since Android 1.0 + */ + public void finish() throws IOException { + if (done) { + return; + } + def.finish(); + int x = 0; + while (!def.finished()) { + if (def.needsInput()) { + def.setInput(buf, 0, 0); + } + x = def.deflate(buf); + out.write(buf, 0, x); + } + done = true; + } + + @Override + public void write(int i) throws IOException { + byte[] b = new byte[1]; + b[0] = (byte) i; + write(b, 0, 1); + } + + /** + * Compresses {@code nbytes} of data from {@code buf} starting at + * {@code off} and writes it to the underlying stream. + * + * @param buffer + * the buffer of data to compress. + * @param off + * offset in buffer to extract data from. + * @param nbytes + * the number of bytes of data to read from the buffer. + * @throws IOException + * If an error occurs during writing. + * @since Android 1.0 + */ + @Override + public void write(byte[] buffer, int off, int nbytes) throws IOException { + if (done) { + throw new IOException(Messages.getString("archive.26")); //$NON-NLS-1$ + } + // avoid int overflow, check null buf + if (off <= buffer.length && nbytes >= 0 && off >= 0 + && buffer.length - off >= nbytes) { + if (!def.needsInput()) { + throw new IOException(); + } + def.setInput(buffer, off, nbytes); + deflate(); + } else { + throw new ArrayIndexOutOfBoundsException(); + } + } +} diff --git a/archive/src/main/java/java/util/zip/GZIPInputStream.java b/archive/src/main/java/java/util/zip/GZIPInputStream.java new file mode 100644 index 0000000..fc70d62 --- /dev/null +++ b/archive/src/main/java/java/util/zip/GZIPInputStream.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.harmony.archive.internal.nls.Messages; + +/** + * The {@code GZIPInputStream} class is used to read data stored in the GZIP + * format, reading and decompressing GZIP data from the underlying stream into + * its buffer. + * + * @since Android 1.0 + */ +public class GZIPInputStream extends java.util.zip.InflaterInputStream { + + /** + * The checksum algorithm used when handling uncompressed data. + * + * @since Android 1.0 + */ + protected CRC32 crc = new CRC32(); + + /** + * Indicates the end of the input stream. + * + * @since Android 1.0 + */ + protected boolean eos = false; + + /** + * The magic header for the GZIP format. + * + * @since Android 1.0 + */ + public final static int GZIP_MAGIC = 0x8b1f; + + private static final int FHCRC = 2; + + private static final int FEXTRA = 4; + + private static final int FNAME = 8; + + private static final int FCOMMENT = 16; + + /** + * Construct a {@code GZIPInputStream} to read from GZIP data from the + * underlying stream. + * + * @param is + * the {@code InputStream} to read data from. + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + public GZIPInputStream(InputStream is) throws IOException { + this(is, BUF_SIZE); + } + + /** + * Construct a {@code GZIPInputStream} to read from GZIP data from the + * underlying stream. Set the internal buffer size to {@code size}. + * + * @param is + * the {@code InputStream} to read data from. + * @param size + * the internal read buffer size. + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + public GZIPInputStream(InputStream is, int size) throws IOException { + super(is, new Inflater(true), size); + byte[] header = new byte[10]; + readFully(header, 0, header.length); + if (getShort(header, 0) != GZIP_MAGIC) { + throw new IOException(Messages.getString("archive.1F")); //$NON-NLS-1$; + } + int flags = header[3]; + boolean hcrc = (flags & FHCRC) != 0; + if (hcrc) { + crc.update(header, 0, header.length); + } + if ((flags & FEXTRA) != 0) { + readFully(header, 0, 2); + if (hcrc) { + crc.update(header, 0, 2); + } + int length = getShort(header, 0); + while (length > 0) { + int max = length > buf.length ? buf.length : length; + int result = in.read(buf, 0, max); + if (result == -1) { + throw new EOFException(); + } + if (hcrc) { + crc.update(buf, 0, result); + } + length -= result; + } + } + if ((flags & FNAME) != 0) { + readZeroTerminated(hcrc); + } + if ((flags & FCOMMENT) != 0) { + readZeroTerminated(hcrc); + } + if (hcrc) { + readFully(header, 0, 2); + int crc16 = getShort(header, 0); + if ((crc.getValue() & 0xffff) != crc16) { + throw new IOException(Messages.getString("archive.20")); //$NON-NLS-1$ + } + crc.reset(); + } + } + + private long getLong(byte[] buffer, int off) { + long l = 0; + l |= (buffer[off] & 0xFF); + l |= (buffer[off + 1] & 0xFF) << 8; + l |= (buffer[off + 2] & 0xFF) << 16; + l |= ((long) (buffer[off + 3] & 0xFF)) << 24; + return l; + } + + private int getShort(byte[] buffer, int off) { + return (buffer[off] & 0xFF) | ((buffer[off + 1] & 0xFF) << 8); + } + + @Override + public int read(byte[] buffer, int off, int nbytes) throws IOException { + if (closed) { + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + if(eof){ + return -1; + } + // avoid int overflow, check null buffer + if (off <= buffer.length && nbytes >= 0 && off >= 0 + && buffer.length - off >= nbytes) { + int val = super.read(buffer, off, nbytes); + if (val != -1) { + crc.update(buffer, off, val); + } else if (!eos) { + eos = true; + // Get non-compressed bytes read by fill + // BEGIN android-changed + // copied from newer version of harmony + int size = inf.getRemaining(); + final int trailerSize = 8; // crc (4 bytes) + total out (4 + // bytes) + byte[] b = new byte[trailerSize]; + int copySize = (size > trailerSize) ? trailerSize : size; + + System.arraycopy(buf, len - size, b, 0, copySize); + readFully(b, copySize, trailerSize - copySize); + // END android-changed + if (getLong(b, 0) != crc.getValue()) { + throw new IOException(Messages.getString("archive.20")); //$NON-NLS-1$ + } + if ((int) getLong(b, 4) != inf.getTotalOut()) { + throw new IOException(Messages.getString("archive.21")); //$NON-NLS-1$ + } + } + return val; + } + throw new ArrayIndexOutOfBoundsException(); + } + + @Override + public void close() throws IOException { + eos = true; + super.close(); + } + + private void readFully(byte[] buffer, int offset, int length) + throws IOException { + int result; + while (length > 0) { + result = in.read(buffer, offset, length); + if (result == -1) { + throw new EOFException(); + } + offset += result; + length -= result; + } + } + + private void readZeroTerminated(boolean hcrc) throws IOException { + int result; + while ((result = in.read()) > 0) { + if (hcrc) { + crc.update(result); + } + } + if (result == -1) { + throw new EOFException(); + } + // Add the zero + if (hcrc) { + crc.update(result); + } + } +} diff --git a/archive/src/main/java/java/util/zip/GZIPOutputStream.java b/archive/src/main/java/java/util/zip/GZIPOutputStream.java new file mode 100644 index 0000000..fa41e19 --- /dev/null +++ b/archive/src/main/java/java/util/zip/GZIPOutputStream.java @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +import java.io.IOException; +import java.io.OutputStream; + +/** + * The {@code GZIPOutputStream} class is used to write data to a stream in the + * GZIP storage format. + * + * @since Android 1.0 + */ +public class GZIPOutputStream extends DeflaterOutputStream { + + /** + * The checksum algorithm used when treating uncompressed data. + * + * @since Android 1.0 + */ + protected CRC32 crc = new CRC32(); + + /** + * Construct a new {@code GZIPOutputStream} to write data in GZIP format to + * the underlying stream. + * + * @param os + * the {@code OutputStream} to write data to. + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + public GZIPOutputStream(OutputStream os) throws IOException { + this(os, BUF_SIZE); + } + + /** + * Construct a new {@code GZIPOutputStream} to write data in GZIP format to + * the underlying stream. Set the internal compression buffer to size + * {@code size}. + * + * @param os + * the {@code OutputStream} to write to. + * @param size + * the internal buffer size. + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + public GZIPOutputStream(OutputStream os, int size) throws IOException { + super(os, new Deflater(Deflater.DEFAULT_COMPRESSION, true), size); + writeShort(GZIPInputStream.GZIP_MAGIC); + out.write(Deflater.DEFLATED); + out.write(0); // flags + writeLong(0); // mod time + out.write(0); // extra flags + out.write(0); // operating system + } + + /** + * Indicates to the stream that all data has been written out, and any GZIP + * terminal data can now be written. + * + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + @Override + public void finish() throws IOException { + super.finish(); + writeLong(crc.getValue()); + writeLong(crc.tbytes); + } + + @Override + public void write(byte[] buffer, int off, int nbytes) throws IOException { + super.write(buffer, off, nbytes); + crc.update(buffer, off, nbytes); + } + + private int writeShort(int i) throws IOException { + out.write(i & 0xFF); + out.write((i >> 8) & 0xFF); + return i; + } + + private long writeLong(long i) throws IOException { + // Write out the long value as an unsigned int + out.write((int) (i & 0xFF)); + out.write((int) (i >> 8) & 0xFF); + out.write((int) (i >> 16) & 0xFF); + out.write((int) (i >> 24) & 0xFF); + return i; + } +} diff --git a/archive/src/main/java/java/util/zip/Inflater.java b/archive/src/main/java/java/util/zip/Inflater.java new file mode 100644 index 0000000..9b93e54 --- /dev/null +++ b/archive/src/main/java/java/util/zip/Inflater.java @@ -0,0 +1,470 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + +import org.apache.harmony.archive.internal.nls.Messages; + +// BEGIN android-added +import java.io.FileDescriptor; +// END android-added + +/** + * This class uncompresses data that was compressed using the <i>DEFLATE</i> + * algorithm (see <a href="http://www.gzip.org/algorithm.txt">specification</a>). + * <p> + * Basically this class is part of the API to the stream based ZLIB compression + * library and is used as such by {@code InflaterInputStream} and its + * descendants. + * </p> + * <p> + * The typical usage of a {@code Inflater} outside this package consists of a + * specific call to one of its constructors before being passed to an instance + * of {@code InflaterInputStream}. + * </p> + * + * @see InflaterInputStream + * @see Deflater + * @since Android 1.0 + */ +public class Inflater { + + private boolean finished; // Set by the inflateImpl native + + private boolean needsDictionary; // Set by the inflateImpl native + + private long streamHandle = -1; + + int inRead; + + int inLength; + + // Fill in the JNI id caches + private static native void oneTimeInitialization(); + + static { + oneTimeInitialization(); + } + + private static final byte MAGIC_NUMBER = 120; + private boolean gotFirstByte = false; + private boolean pass_magic_number_check = true; + + /** + * Release any resources associated with this {@code Inflater}. Any unused + * input/output is discarded. This is also called by the finalize method. + * + * @since Android 1.0 + */ + public synchronized void end() { + if (streamHandle != -1) { + endImpl(streamHandle); + inRead = 0; + inLength = 0; + streamHandle = -1; + } + } + + private native synchronized void endImpl(long handle); + + @Override + protected void finalize() { + end(); + } + + /** + * Indicates if the {@code Inflater} has inflated the entire deflated + * stream. If deflated bytes remain and {@code needsInput()} returns {@code + * true} this method will return {@code false}. This method should be + * called after all deflated input is supplied to the {@code Inflater}. + * + * @return {@code true} if all input has been inflated, {@code false} + * otherwise. + * @since Android 1.0 + */ + public synchronized boolean finished() { + return finished; + } + + /** + * Returns the <i>Adler32</i> checksum of either all bytes inflated, or the + * checksum of the preset dictionary if one has been supplied. + * + * @return The <i>Adler32</i> checksum associated with this + * {@code Inflater}. + * @since Android 1.0 . + */ + public synchronized int getAdler() { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + return getAdlerImpl(streamHandle); + } + + private native synchronized int getAdlerImpl(long handle); + + /** + * Returns the number of bytes of current input remaining to be read by the + * inflater. + * + * @return the number of bytes of unread input. + * @since Android 1.0 + */ + public synchronized int getRemaining() { + return inLength - inRead; + } + + /** + * Returns total number of bytes of input read by the {@code Inflater}. The + * result value is limited by {@code Integer.MAX_VALUE}. + * + * @return the total number of bytes read. + * @since Android 1.0 + */ + public synchronized int getTotalIn() { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + long totalIn = getTotalInImpl(streamHandle); + return (totalIn <= Integer.MAX_VALUE ? (int) totalIn + : Integer.MAX_VALUE); + } + + private synchronized native long getTotalInImpl(long handle); + + /** + * Returns total number of bytes written to the output buffer by the {@code + * Inflater}. The result value is limited by {@code Integer.MAX_VALUE}. + * + * @return the total bytes of output data written. + * @since Android 1.0 + */ + public synchronized int getTotalOut() { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + long totalOut = getTotalOutImpl(streamHandle); + return (totalOut <= Integer.MAX_VALUE ? (int) totalOut + : Integer.MAX_VALUE); + } + + private native synchronized long getTotalOutImpl(long handle); + + /** + * Inflates bytes from current input and stores them in {@code buf}. + * + * @param buf + * the buffer where decompressed data bytes are written. + * @return the number of bytes inflated. + * @throws DataFormatException + * if the underlying stream is corrupted or was not compressed + * using a {@code Deflater}. + * @since Android 1.0 + */ + public int inflate(byte[] buf) throws DataFormatException { + return inflate(buf, 0, buf.length); + } + + /** + * Inflates up to n bytes from the current input and stores them in {@code + * buf} starting at {@code off}. + * + * @param buf + * the buffer to write inflated bytes to. + * @param off + * the offset in buffer where to start writing decompressed data. + * @param nbytes + * the number of inflated bytes to write to {@code buf}. + * @throws DataFormatException + * if the underlying stream is corrupted or was not compressed + * using a {@code Deflater}. + * @return the number of bytes inflated. + */ + public synchronized int inflate(byte[] buf, int off, int nbytes) + throws DataFormatException { + // avoid int overflow, check null buf + if (off <= buf.length && nbytes >= 0 && off >= 0 + && buf.length - off >= nbytes) { + if (nbytes == 0) + return 0; + + if (streamHandle == -1) { + throw new IllegalStateException(); + } + + if (!pass_magic_number_check) { + throw new DataFormatException(); + } + + if (needsInput()) { + return 0; + } + + boolean neededDict = needsDictionary; + needsDictionary = false; + int result = inflateImpl(buf, off, nbytes, streamHandle); + if (needsDictionary && neededDict) { + throw new DataFormatException(Messages.getString("archive.27")); //$NON-NLS-1$ + } + return result; + } + throw new ArrayIndexOutOfBoundsException(); + } + + private native synchronized int inflateImpl(byte[] buf, int off, + int nbytes, long handle); + + /** + * This constructor creates an inflater that expects a header from the input + * stream. Use {@code Inflater(boolean)} if the input comes without a ZLIB + * header. + * + * @since Android 1.0 + * @since Android 1.0 + */ + public Inflater() { + this(false); + } + + /** + * This constructor allows to create an inflater that expects no header from + * the input stream. + * + * @param noHeader + * {@code true} indicates that no ZLIB header comes with the + * input. + * @since Android 1.0 + */ + public Inflater(boolean noHeader) { + streamHandle = createStream(noHeader); + } + + /** + * Indicates whether the input bytes were compressed with a preset + * dictionary. This method should be called prior to {@code inflate()} to + * determine whether a dictionary is required. If so {@code setDictionary()} + * should be called with the appropriate dictionary prior to calling {@code + * inflate()}. + * + * @return {@code true} if a preset dictionary is required for inflation. + * @see #setDictionary(byte[]) + * @see #setDictionary(byte[], int, int) + * @since Android 1.0 + */ + public synchronized boolean needsDictionary() { + return needsDictionary; + } + + /** + * Indicates that input has to be passed to the inflater. + * + * @return {@code true} if {@code setInput} has to be called before + * inflation can proceed. + * @see #setInput(byte[]) + * @since Android 1.0 + */ + public synchronized boolean needsInput() { + return inRead == inLength; + } + + /** + * Resets the {@code Inflater}. Should be called prior to inflating a new + * set of data. + * + * @since Android 1.0 + */ + public synchronized void reset() { + if (streamHandle == -1) { + throw new NullPointerException(); + } + finished = false; + needsDictionary = false; + inLength = inRead = 0; + resetImpl(streamHandle); + } + + private native synchronized void resetImpl(long handle); + + /** + * Sets the preset dictionary to be used for inflation to {@code buf}. + * {@code needsDictionary()} can be called to determine whether the current + * input was deflated using a preset dictionary. + * + * @param buf + * The buffer containing the dictionary bytes. + * @see #needsDictionary + * @since Android 1.0 + */ + public synchronized void setDictionary(byte[] buf) { + setDictionary(buf, 0, buf.length); + } + + /** + * Like {@code setDictionary(byte[])}, allowing to define a specific region + * inside {@code buf} to be used as a dictionary. + * + * @param buf + * the buffer containing the dictionary data bytes. + * @param off + * the offset of the data. + * @param nbytes + * the length of the data. + * @see #needsDictionary + * @since Android 1.0 + */ + public synchronized void setDictionary(byte[] buf, int off, int nbytes) { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + // avoid int overflow, check null buf + if (off <= buf.length && nbytes >= 0 && off >= 0 + && buf.length - off >= nbytes) { + setDictionaryImpl(buf, off, nbytes, streamHandle); + } else { + throw new ArrayIndexOutOfBoundsException(); + } + } + + private native synchronized void setDictionaryImpl(byte[] buf, int off, + int nbytes, long handle); + + /** + * Sets the current input to to be decrompressed. This method should only be + * called if {@code needsInput()} returns {@code true}. + * + * @param buf + * the input buffer. + * @see #needsInput + * @since Android 1.0 + */ + public synchronized void setInput(byte[] buf) { + setInput(buf, 0, buf.length); + } + + /** + * Sets the current input to the region of the input buffer starting at + * {@code off} and ending at {@code nbytes - 1} where data is written after + * decompression. This method should only be called if {@code needsInput()} + * returns {@code true}. + * + * @param buf + * the input buffer. + * @param off + * the offset to read from the input buffer. + * @param nbytes + * the number of bytes to read. + * @see #needsInput + * @since Android 1.0 + */ + public synchronized void setInput(byte[] buf, int off, int nbytes) { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + // avoid int overflow, check null buf + if (off <= buf.length && nbytes >= 0 && off >= 0 + && buf.length - off >= nbytes) { + inRead = 0; + inLength = nbytes; + setInputImpl(buf, off, nbytes, streamHandle); + } else { + throw new ArrayIndexOutOfBoundsException(); + } + // BEGIN android-note + // Note: pass_magic_number_check is set to false when setInput is + // called for the first time and for a single byte. + // Since setInput is called only by InflaterInputStream.fill + // with an arbitrary byte len this check seems quite useless. + // FIXME: We should find out whether the first byte has to be the magic + // number in all cases and correct the check as well as place it + // in setFileInput accordingly. + // And at a first glance it doesn't look like the first byte has + // to be 120. + // END android-note + if(!gotFirstByte && nbytes>0) + { + pass_magic_number_check = (buf[off] == MAGIC_NUMBER || nbytes > 1); + gotFirstByte = true; + } + } + + // BEGIN android-added + /** + * Sets the current input to the region within a file starting at {@code + * off} and ending at {@code nbytes - 1}. This method should only be called + * if {@code needsInput()} returns {@code true}. + * + * @param file + * the input file. + * @param off + * the offset to read from in buffer. + * @param nbytes + * the number of bytes to read. + * @see #needsInput + * @since Android 1.0 + */ + synchronized int setFileInput(FileDescriptor fd, long off, int nbytes) { + if (streamHandle == -1) { + throw new IllegalStateException(); + } + inRead = 0; + inLength = setFileInputImpl(fd, off, nbytes, streamHandle); + return inLength; + } + // END android-added + + /** + * Returns the total number of bytes read by the {@code Inflater}. This + * method performs the same as {@code getTotalIn()} except that it returns a + * {@code long} value instead of an integer. + * + * @return the total number of bytes read. + * @since Android 1.0 + */ + public synchronized long getBytesRead() { + // Throw NPE here + if (streamHandle == -1) { + throw new NullPointerException(); + } + return getTotalInImpl(streamHandle); + } + + /** + * Returns a the total number of bytes read by the {@code Inflater}. This + * method performs the same as {@code getTotalOut} except it returns a + * {@code long} value instead of an integer. + * + * @return the total bytes written to the output buffer. + * @since Android 1.0 + */ + public synchronized long getBytesWritten() { + // Throw NPE here + if (streamHandle == -1) { + throw new NullPointerException(); + } + return getTotalOutImpl(streamHandle); + } + + private native synchronized void setInputImpl(byte[] buf, int off, + int nbytes, long handle); + + // BEGIN android-added + private native synchronized int setFileInputImpl(FileDescriptor fd, long off, + int nbytes, long handle); + // END android-added + + private native long createStream(boolean noHeader1); +} diff --git a/archive/src/main/java/java/util/zip/InflaterInputStream.java b/archive/src/main/java/java/util/zip/InflaterInputStream.java new file mode 100644 index 0000000..5d3bda0 --- /dev/null +++ b/archive/src/main/java/java/util/zip/InflaterInputStream.java @@ -0,0 +1,353 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +import java.io.EOFException; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.harmony.archive.internal.nls.Messages; + +/** + * This class provides an implementation of {@code FilterInputStream} that + * uncompresses data that was compressed using the <i>DEFLATE</i> algorithm + * (see <a href="http://www.gzip.org/algorithm.txt">specification</a>). + * Basically it wraps the {@code Inflater} class and takes care of the + * buffering. + * + * @see Inflater + * @see DeflaterOutputStream + * @since Android 1.0 + */ +public class InflaterInputStream extends FilterInputStream { + + /** + * The inflater used for this stream. + * + * @since Android 1.0 + */ + protected Inflater inf; + + /** + * The input buffer used for decompression. + * + * @since Android 1.0 + */ + protected byte[] buf; + + /** + * The length of the buffer. + * + * @since Android 1.0 + */ + protected int len; + + boolean closed; + + boolean eof; + + static final int BUF_SIZE = 512; + + // BEGIN android-added + int nativeEndBufSize = 0; + // END android-added + + /** + * This is the most basic constructor. You only need to pass the {@code + * InputStream} from which the compressed data is to be read from. Default + * settings for the {@code Inflater} and internal buffer are be used. In + * particular the Inflater expects a ZLIB header from the input stream. + * + * @param is + * the {@code InputStream} to read data from. + * @since Android 1.0 + */ + public InflaterInputStream(InputStream is) { + this(is, new Inflater(), BUF_SIZE); + } + + /** + * This constructor lets you pass a specifically initialized Inflater, + * for example one that expects no ZLIB header. + * + * @param is + * the {@code InputStream} to read data from. + * @param inf + * the specific {@code Inflater} for uncompressing data. + * @since Android 1.0 + */ + public InflaterInputStream(InputStream is, Inflater inf) { + this(is, inf, BUF_SIZE); + } + + /** + * This constructor lets you specify both the {@code Inflater} as well as + * the internal buffer size to be used. + * + * @param is + * the {@code InputStream} to read data from. + * @param inf + * the specific {@code Inflater} for uncompressing data. + * @param bsize + * the size to be used for the internal buffer. + * @since Android 1.0 + */ + public InflaterInputStream(InputStream is, Inflater inf, int bsize) { + super(is); + if (is == null || inf == null) { + throw new NullPointerException(); + } + if (bsize <= 0) { + throw new IllegalArgumentException(); + } + this.inf = inf; + // BEGIN android-changed + if (is instanceof ZipFile.RAFStream) { + nativeEndBufSize = bsize; + } else { + buf = new byte[bsize]; + } + // END android-changed + } + + /** + * Reads a single byte of decompressed data. + * + * @return the byte read. + * @throws IOException + * if an error occurs reading the byte. + * @since Android 1.0 + */ + @Override + public int read() throws IOException { + byte[] b = new byte[1]; + if (read(b, 0, 1) == -1) { + return -1; + } + return b[0] & 0xff; + } + + /** + * Reads up to {@code nbytes} of decompressed data and stores it in + * {@code buffer} starting at {@code off}. + * + * @param buffer + * the buffer to write data to. + * @param off + * offset in buffer to start writing. + * @param nbytes + * number of bytes to read. + * @return Number of uncompressed bytes read + * @throws IOException + * if an IOException occurs. + * @since Android 1.0 + */ + @Override + public int read(byte[] buffer, int off, int nbytes) throws IOException { + /* archive.1E=Stream is closed */ + if (closed) { + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + + if (null == buffer) { + throw new NullPointerException(); + } + + if (off < 0 || nbytes < 0 || off + nbytes > buffer.length) { + throw new IndexOutOfBoundsException(); + } + + if (nbytes == 0) { + return 0; + } + + if (inf.finished()) { + eof = true; + return -1; + } + + // avoid int overflow, check null buffer + if (off <= buffer.length && nbytes >= 0 && off >= 0 + && buffer.length - off >= nbytes) { + do { + if (inf.needsInput()) { + fill(); + } + int result; + try { + result = inf.inflate(buffer, off, nbytes); + } catch (DataFormatException e) { + if (len == -1) { + throw new EOFException(); + } + throw (IOException)(new IOException().initCause(e)); + } + if (result > 0) { + return result; + } else if (inf.finished()) { + eof = true; + return -1; + } else if (inf.needsDictionary()) { + return -1; + } else if (len == -1) { + throw new EOFException(); + // If result == 0, fill() and try again + } + } while (true); + } + throw new ArrayIndexOutOfBoundsException(); + } + + + /** + * Fills the input buffer with data to be decompressed. + * + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + protected void fill() throws IOException { + if (closed) { + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + // BEGIN android-changed + if (nativeEndBufSize > 0) { + ZipFile.RAFStream is = (ZipFile.RAFStream)in; + synchronized (is.mSharedRaf) { + long len = is.mLength - is.mOffset; + if (len > nativeEndBufSize) len = nativeEndBufSize; + int cnt = inf.setFileInput(is.mSharedRaf.getFD(), is.mOffset, (int)nativeEndBufSize); + is.skip(cnt); + } + } else { + if ((len = in.read(buf)) > 0) { + inf.setInput(buf, 0, len); + } + } + // END android-changed + } + + /** + * Skips up to n bytes of uncompressed data. + * + * @param nbytes + * the number of bytes to skip. + * @return the number of uncompressed bytes skipped. + * @throws IOException + * if an error occurs skipping. + * @since Android 1.0 + */ + @Override + public long skip(long nbytes) throws IOException { + if (nbytes >= 0) { + long count = 0, rem = 0; + while (count < nbytes) { + int x = read(buf, 0, + (rem = nbytes - count) > buf.length ? buf.length + : (int) rem); + if (x == -1) { + eof = true; + return count; + } + count += x; + } + return count; + } + throw new IllegalArgumentException(); + } + + /** + * Returns whether data can be read from this stream. + * + * @return 0 if this stream has been closed, 1 otherwise. + * @throws IOException + * If an error occurs. + * @since Android 1.0 + */ + @Override + public int available() throws IOException { + if (closed) { + // archive.1E=Stream is closed + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + if (eof) { + return 0; + } + return 1; + } + + /** + * Closes the input stream. + * + * @throws IOException + * If an error occurs closing the input stream. + * @since Android 1.0 + */ + @Override + public void close() throws IOException { + if (!closed) { + inf.end(); + closed = true; + eof = true; + super.close(); + } + } + + /** + * This implementation overrides the super type implementation to do nothing + * at all. + * + * @param readlimit + * of no use. + * @since Android 1.0 + */ + @Override + @SuppressWarnings("unused") + public void mark(int readlimit) { + // do nothing + } + + /** + * Reset the position of the stream to the last marked position. This + * implementation overrides the supertype implementation and always throws + * an {@link IOException IOException} when called. + * + * @throws IOException + * if the method is called + * @since Android 1.0 + */ + @Override + public void reset() throws IOException{ + throw new IOException(); + } + + /** + * Returns whether the receiver implements {@code mark} semantics. This type + * does not support {@code mark()}, so always responds {@code false}. + * + * @return false, always + * @since Android 1.0 + */ + @Override + public boolean markSupported() { + return false; + } + +} diff --git a/archive/src/main/java/java/util/zip/ZipConstants.java b/archive/src/main/java/java/util/zip/ZipConstants.java new file mode 100644 index 0000000..d804b0e --- /dev/null +++ b/archive/src/main/java/java/util/zip/ZipConstants.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +interface ZipConstants { + + public static final long LOCSIG = 0x4034b50, EXTSIG = 0x8074b50, + CENSIG = 0x2014b50, ENDSIG = 0x6054b50; + + public static final int LOCHDR = 30, EXTHDR = 16, CENHDR = 46, ENDHDR = 22, + LOCVER = 4, LOCFLG = 6, LOCHOW = 8, LOCTIM = 10, LOCCRC = 14, + LOCSIZ = 18, LOCLEN = 22, LOCNAM = 26, LOCEXT = 28, EXTCRC = 4, + EXTSIZ = 8, EXTLEN = 12, CENVEM = 4, CENVER = 6, CENFLG = 8, + CENHOW = 10, CENTIM = 12, CENCRC = 16, CENSIZ = 20, CENLEN = 24, + CENNAM = 28, CENEXT = 30, CENCOM = 32, CENDSK = 34, CENATT = 36, + CENATX = 38, CENOFF = 42, ENDSUB = 8, ENDTOT = 10, ENDSIZ = 12, + ENDOFF = 16, ENDCOM = 20; +} diff --git a/archive/src/main/java/java/util/zip/ZipEntry.java b/archive/src/main/java/java/util/zip/ZipEntry.java new file mode 100644 index 0000000..2cc7a9c --- /dev/null +++ b/archive/src/main/java/java/util/zip/ZipEntry.java @@ -0,0 +1,781 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + +// BEGIN android-added +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.RandomAccessFile; +import java.io.UnsupportedEncodingException; +// END android-added +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +/** + * An instance of {@code ZipEntry} represents an entry within a <i>ZIP-archive</i>. + * An entry has attributes such as name (= path) or the size of its data. While + * an entry identifies data stored in an archive, it does not hold the data + * itself. For example when reading a <i>ZIP-file</i> you will first retrieve + * all its entries in a collection and then read the data for a specific entry + * through an input stream. + * + * @see ZipFile + * @see ZipOutputStream + * @since Android 1.0 + */ +public class ZipEntry implements ZipConstants, Cloneable { + String name, comment; + + long compressedSize = -1, crc = -1, size = -1; + // BEGIN android-removed + // long dataOffset = -1; + // END android-removed + + int compressionMethod = -1, time = -1, modDate = -1; + + byte[] extra; + + // BEGIN android-added + /* + * Fields, present in the Central Directory Entry and Local File Entry. + * + * Not all of these are part of the interface, but we need them if we + * want to be able to copy entries from one archive to another without + * losing any meta-data. + * + * We use over-sized fields so we can indicate whether a field has been + * initialized or not. + */ + private int mVersionMadeBy; // CDE + private int mVersionToExtract; // CDE, LFE + private int mGPBitFlag; // CDE, LFE + // private int mCompressionMethod; // CDE, LFE = compressionMethod + // private int mLastModFileTime; // CDE, LFE = time + // private int mLastModFileDate; // CDE, LFE = modDate + // private long mCRC32; // CDE, LFE = crc + // private long mCompressedSize; // CDE, LFE = compressedSize + // private long mUncompressedSize; // CDE, LFE = size + int nameLen, extraLen, commentLen; + //private int mFileNameLength; // CDE, LFE + //private int mExtraFieldLength; // CDE, LFE + //private int mFileCommentLength; // CDE + private int mDiskNumberStart; // CDE + private int mInternalAttrs; // CDE + private long mExternalAttrs; // CDE + long mLocalHeaderRelOffset; // CDE ? dataOffset + // private String mFileName; // CDE, LFE = name + // private byte[] mExtraField; // CDE, LFE = extra + // private String mFileComment; // CDE = comment + + + // GPBitFlag 3: uses a Data Descriptor block (need for deflated data) + /*package*/ static final int USES_DATA_DESCR = 0x0008; + + // private static Calendar mCalendar = Calendar.getInstance(); + // END android-added + + /** + * Zip entry state: Deflated. + * + * @since Android 1.0 + */ + public static final int DEFLATED = 8; + + /** + * Zip entry state: Stored. + * + * @since Android 1.0 + */ + public static final int STORED = 0; + + /** + * Constructs a new {@code ZipEntry} with the specified name. + * + * @param name + * the name of the ZIP entry. + * @throws IllegalArgumentException + * if the name length is outside the range (> 0xFFFF). + * @since Android 1.0 + */ + public ZipEntry(String name) { + if (name == null) { + throw new NullPointerException(); + } + if (name.length() > 0xFFFF) { + throw new IllegalArgumentException(); + } + this.name = name; + + // BEGIN android-added + mVersionMadeBy = 0x0317; // 03=UNIX, 17=spec v2.3 + mVersionToExtract = 20; // need deflate, not much else + mGPBitFlag = 0; + compressionMethod = -1; + time = -1; + modDate = -1; + crc = -1L; + compressedSize = -1L; + size = -1L; + extraLen = -1; + nameLen = -1; + mDiskNumberStart = 0; + mInternalAttrs = 0; + mExternalAttrs = 0x81b60020L; // matches WinZip + mLocalHeaderRelOffset = -1; + extra = null; + comment = null; + // END android-added + } + + /** + * Gets the comment for this {@code ZipEntry}. + * + * @return the comment for this {@code ZipEntry}, or {@code null} if there + * is no comment. If we're reading an archive with + * {@code ZipInputStream} the comment is not available. + * @since Android 1.0 + */ + public String getComment() { + return comment; + } + + /** + * Gets the compressed size of this {@code ZipEntry}. + * + * @return the compressed size, or -1 if the compressed size has not been + * set. + * @since Android 1.0 + */ + public long getCompressedSize() { + return compressedSize; + } + + /** + * Gets the checksum for this {@code ZipEntry}. + * + * @return the checksum, or -1 if the checksum has not been set. + * @since Android 1.0 + */ + public long getCrc() { + return crc; + } + + /** + * Gets the extra information for this {@code ZipEntry}. + * + * @return a byte array containing the extra information, or {@code null} if + * there is none. + * @since Android 1.0 + */ + public byte[] getExtra() { + return extra; + } + + /** + * Gets the compression method for this {@code ZipEntry}. + * + * @return the compression method, either {@code DEFLATED}, {@code STORED} + * or -1 if the compression method has not been set. + * @since Android 1.0 + */ + public int getMethod() { + return compressionMethod; + } + + /** + * Gets the name of this {@code ZipEntry}. + * + * @return the entry name. + * @since Android 1.0 + */ + public String getName() { + return name; + } + + /** + * Gets the uncompressed size of this {@code ZipEntry}. + * + * @return the uncompressed size, or {@code -1} if the size has not been + * set. + * @since Android 1.0 + */ + public long getSize() { + return size; + } + + /** + * Gets the last modification time of this {@code ZipEntry}. + * + * @return the last modification time as the number of milliseconds since + * Jan. 1, 1970. + * @since Android 1.0 + */ + public long getTime() { + if (time != -1) { + GregorianCalendar cal = new GregorianCalendar(); + cal.set(Calendar.MILLISECOND, 0); + cal.set(1980 + ((modDate >> 9) & 0x7f), ((modDate >> 5) & 0xf) - 1, + modDate & 0x1f, (time >> 11) & 0x1f, (time >> 5) & 0x3f, + (time & 0x1f) << 1); + return cal.getTime().getTime(); + } + return -1; + } + + /** + * Determine whether or not this {@code ZipEntry} is a directory. + * + * @return {@code true} when this {@code ZipEntry} is a directory, {@code + * false} otherwise. + * @since Android 1.0 + */ + public boolean isDirectory() { + return name.charAt(name.length() - 1) == '/'; + } + + /** + * Sets the comment for this {@code ZipEntry}. + * + * @param string + * the comment for this entry. + * @since Android 1.0 + */ + public void setComment(String string) { + if (string == null || string.length() <= 0xFFFF) { + comment = string; + } else { + throw new IllegalArgumentException(); + } + } + + /** + * Sets the compressed size for this {@code ZipEntry}. + * + * @param value + * the compressed size (in bytes). + * @since Android 1.0 + */ + public void setCompressedSize(long value) { + compressedSize = value; + } + + /** + * Sets the checksum for this {@code ZipEntry}. + * + * @param value + * the checksum for this entry. + * @throws IllegalArgumentException + * if {@code value} is < 0 or > 0xFFFFFFFFL. + * @since Android 1.0 + */ + public void setCrc(long value) { + if (value >= 0 && value <= 0xFFFFFFFFL) { + crc = value; + } else { + throw new IllegalArgumentException(); + } + } + + /** + * Sets the extra information for this {@code ZipEntry}. + * + * @param data + * a byte array containing the extra information. + * @throws IllegalArgumentException + * when the length of data is greater than 0xFFFF bytes. + * @since Android 1.0 + */ + public void setExtra(byte[] data) { + if (data == null || data.length <= 0xFFFF) { + extra = data; + } else { + throw new IllegalArgumentException(); + } + } + + /** + * Sets the compression method for this {@code ZipEntry}. + * + * @param value + * the compression method, either {@code DEFLATED} or {@code + * STORED}. + * @throws IllegalArgumentException + * when value is not {@code DEFLATED} or {@code STORED}. + * @since Android 1.0 + */ + public void setMethod(int value) { + if (value != STORED && value != DEFLATED) { + throw new IllegalArgumentException(); + } + compressionMethod = value; + } + + /** + * Sets the uncompressed size of this {@code ZipEntry}. + * + * @param value + * the uncompressed size for this entry. + * @throws IllegalArgumentException + * if {@code value} < 0 or {@code value} > 0xFFFFFFFFL. + * @since Android 1.0 + */ + public void setSize(long value) { + if (value >= 0 && value <= 0xFFFFFFFFL) { + size = value; + } else { + throw new IllegalArgumentException(); + } + } + + /** + * Sets the modification time of this {@code ZipEntry}. + * + * @param value + * the modification time as the number of milliseconds since Jan. + * 1, 1970. + * @since Android 1.0 + */ + public void setTime(long value) { + GregorianCalendar cal = new GregorianCalendar(); + cal.setTime(new Date(value)); + int year = cal.get(Calendar.YEAR); + if (year < 1980) { + modDate = 0x21; + time = 0; + } else { + modDate = cal.get(Calendar.DATE); + modDate = (cal.get(Calendar.MONTH) + 1 << 5) | modDate; + modDate = ((cal.get(Calendar.YEAR) - 1980) << 9) | modDate; + time = cal.get(Calendar.SECOND) >> 1; + time = (cal.get(Calendar.MINUTE) << 5) | time; + time = (cal.get(Calendar.HOUR_OF_DAY) << 11) | time; + } + } + + /** + * Returns the string representation of this {@code ZipEntry}. + * + * @return the string representation of this {@code ZipEntry}. + * @since Android 1.0 + */ + @Override + public String toString() { + return name; + } + + // BEGIN android-removed + // ZipEntry(String name, String comment, byte[] extra, long modTime, + // long size, long compressedSize, long crc, int compressionMethod, + // long modDate, long offset) { + // this.name = name; + // this.comment = comment; + // this.extra = extra; + // time = (int) modTime; + // this.size = size; + // this.compressedSize = compressedSize; + // this.crc = crc; + // this.compressionMethod = compressionMethod; + // this.modDate = (int) modDate; + // dataOffset = offset; + // } + // END android-removed + + /** + * Constructs a new {@code ZipEntry} using the values obtained from {@code + * ze}. + * + * @param ze + * the {@code ZipEntry} from which to obtain values. + * @since Android 1.0 + */ + public ZipEntry(ZipEntry ze) { + name = ze.name; + comment = ze.comment; + time = ze.time; + size = ze.size; + compressedSize = ze.compressedSize; + crc = ze.crc; + compressionMethod = ze.compressionMethod; + modDate = ze.modDate; + extra = ze.extra; + // BEGIN android-removed + // dataOffset = ze.dataOffset; + // END android-removed + // BEGIN android-added + mVersionMadeBy = ze.mVersionMadeBy; + mVersionToExtract = ze.mVersionToExtract; + mGPBitFlag = ze.mGPBitFlag; + extraLen = ze.extraLen; + nameLen = ze.nameLen; + mDiskNumberStart = ze.mDiskNumberStart; + mInternalAttrs = ze.mInternalAttrs; + mExternalAttrs = ze.mExternalAttrs; + mLocalHeaderRelOffset = ze.mLocalHeaderRelOffset; + // END android-added + } + + /** + * Returns a shallow copy of this entry. + * + * @return a copy of this entry. + * @since Android 1.0 + */ + @Override + public Object clone() { + return new ZipEntry(this); + } + + /** + * Returns the hash code for this {@code ZipEntry}. + * + * @return the hash code of the entry. + * @since Android 1.0 + */ + @Override + public int hashCode() { + return name.hashCode(); + } + + // BEGIN android-added + // readShortLE is not used. + // readIntLE is used only once in ZipFile. + /* + * Internal constructor. Creates a new ZipEntry by reading the + * Central Directory Entry from "in", which must be positioned at + * the CDE signature. + * + * On exit, "in" will be positioned at the start of the next entry. + */ + /*package*/ ZipEntry(LittleEndianReader ler, InputStream in) throws IOException { + + /* + * We're seeing performance issues when we call readShortLE and + * readIntLE, so we're going to read the entire header at once + * and then parse the results out without using any function calls. + * Uglier, but should be much faster. + * + * Note that some lines look a bit different, because the corresponding + * fields or locals are long and so we need to do & 0xffffffffl to avoid + * problems induced by sign extension. + */ + + byte[] hdrBuf = ler.hdrBuf; + myReadFully(in, hdrBuf); + + long sig = (hdrBuf[0] & 0xff) | ((hdrBuf[1] & 0xff) << 8) | + ((hdrBuf[2] & 0xff) << 16) | ((hdrBuf[3] << 24) & 0xffffffffL); + if (sig != CENSIG) + throw new ZipException("Central Directory Entry not found"); + + mVersionMadeBy = (hdrBuf[4] & 0xff) | ((hdrBuf[5] & 0xff) << 8); + mVersionToExtract = (hdrBuf[6] & 0xff) | ((hdrBuf[7] & 0xff) << 8); + mGPBitFlag = (hdrBuf[8] & 0xff) | ((hdrBuf[9] & 0xff) << 8); + compressionMethod = (hdrBuf[10] & 0xff) | ((hdrBuf[11] & 0xff) << 8); + time = (hdrBuf[12] & 0xff) | ((hdrBuf[13] & 0xff) << 8); + modDate = (hdrBuf[14] & 0xff) | ((hdrBuf[15] & 0xff) << 8); + crc = (hdrBuf[16] & 0xff) | ((hdrBuf[17] & 0xff) << 8) + | ((hdrBuf[18] & 0xff) << 16) + | ((hdrBuf[19] << 24) & 0xffffffffL); + compressedSize = (hdrBuf[20] & 0xff) | ((hdrBuf[21] & 0xff) << 8) + | ((hdrBuf[22] & 0xff) << 16) + | ((hdrBuf[23] << 24) & 0xffffffffL); + size = (hdrBuf[24] & 0xff) | ((hdrBuf[25] & 0xff) << 8) + | ((hdrBuf[26] & 0xff) << 16) + | ((hdrBuf[27] << 24) & 0xffffffffL); + nameLen = (hdrBuf[28] & 0xff) | ((hdrBuf[29] & 0xff) << 8); + extraLen = (hdrBuf[30] & 0xff) | ((hdrBuf[31] & 0xff) << 8); + commentLen = (hdrBuf[32] & 0xff) | ((hdrBuf[33] & 0xff) << 8); + mDiskNumberStart = (hdrBuf[34] & 0xff) | ((hdrBuf[35] & 0xff) << 8); + mInternalAttrs = (hdrBuf[36] & 0xff) | ((hdrBuf[37] & 0xff) << 8); + mExternalAttrs = (hdrBuf[38] & 0xff) | ((hdrBuf[39] & 0xff) << 8) + | ((hdrBuf[40] & 0xff) << 16) + | ((hdrBuf[41] << 24) & 0xffffffffL); + mLocalHeaderRelOffset = (hdrBuf[42] & 0xff) | ((hdrBuf[43] & 0xff) << 8) + | ((hdrBuf[44] & 0xff) << 16) + | ((hdrBuf[45] << 24) & 0xffffffffL); + + byte[] nameBytes = new byte[nameLen]; + myReadFully(in, nameBytes); + + byte[] commentBytes = null; + if (commentLen > 0) { + commentBytes = new byte[commentLen]; + myReadFully(in, commentBytes); + } + + if (extraLen > 0) { + extra = new byte[extraLen]; + myReadFully(in, extra); + } + + try { + /* + * The actual character set is "IBM Code Page 437". As of + * Sep 2006, the Zip spec (APPNOTE.TXT) supports UTF-8. When + * bit 11 of the GP flags field is set, the file name and + * comment fields are UTF-8. + * + * TODO: add correct UTF-8 support. + */ + name = new String(nameBytes, "ISO-8859-1"); + if (commentBytes != null) + comment = new String(commentBytes, "ISO-8859-1"); + else + comment = null; + } + catch (UnsupportedEncodingException uee) { + throw new InternalError(uee.getMessage()); + } + } + private void myReadFully(InputStream in, byte[] b) throws IOException { + int count; + int len = b.length; + int off = 0; + + while (len > 0) { + count = in.read(b, off, len); + if (count <= 0) + throw new EOFException(); + off += count; + len -= count; + } + } + + /*package*/ void setVersionToExtract(int version) { + mVersionToExtract = version; + } + + /*package*/ int getGPBitFlag() { + return mGPBitFlag; + } + /*package*/ void setGPBitFlag(int flags) { + mGPBitFlag = flags; + } + + /*package*/ long getLocalHeaderRelOffset() { + return mLocalHeaderRelOffset; + } + /*package*/ void setLocalHeaderRelOffset(long offset) { + mLocalHeaderRelOffset = offset; + } + + /*package*/ void setDateTime(int lastModFileDate, int lastModFileTime) { + time = lastModFileTime; + modDate = lastModFileDate; + } + + /* + * Read a two-byte short in little-endian order. + * + * The DataInput interface, which RandomAccessFile implements, provides + * a readInt() function. Unfortunately, it's defined to use big-endian. + */ + /*package*/ static int readShortLE(RandomAccessFile raf) throws IOException + { + int b0, b1; + + b0 = raf.read(); + b1 = raf.read(); + if (b1 < 0) + throw new EOFException("in ZipEntry.readShortLE(RandomAccessFile)"); + return b0 | (b1 << 8); + } + + /* + * Read a four-byte int in little-endian order. + */ + /*package*/ static long readIntLE(RandomAccessFile raf) throws IOException + { + int b0, b1, b2, b3; + + b0 = raf.read(); + b1 = raf.read(); + b2 = raf.read(); + b3 = raf.read(); + if (b3 < 0) + throw new EOFException("in ZipEntry.readIntLE(RandomAccessFile)"); + return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24); // ATTENTION: DOES SIGN EXTENSION: IS THIS WANTED? + } + + static class LittleEndianReader { + private byte[] b = new byte[4]; + byte[] hdrBuf = new byte[CENHDR]; + + /* + * Read a two-byte short in little-endian order. + */ + int readShortLE(InputStream in) throws IOException { + if (in.read(b, 0, 2) == 2) { + return (b[0] & 0XFF) | ((b[1] & 0XFF) << 8); + } else { + throw new EOFException("in ZipEntry.readShortLE(InputStream)"); + } + } + + /* + * Read a four-byte int in little-endian order. + */ + long readIntLE(InputStream in) throws IOException { + if (in.read(b, 0, 4) == 4) { + return ( ((b[0] & 0XFF)) + | ((b[1] & 0XFF) << 8) + | ((b[2] & 0XFF) << 16) + | ((b[3] & 0XFF) << 24)) + & 0XFFFFFFFFL; // Here for sure NO sign extension is wanted. + } else { + throw new EOFException("in ZipEntry.readIntLE(InputStream)"); + } + } + } + + /* + * Write a two-byte short in little-endian order. + */ + /*package*/ static void writeShortLE(OutputStream out, int val) + throws IOException + { + out.write(val & 0xff); + out.write((val >> 8) & 0xff); + } + + /* + * Write a 4-byte int in little-endian order. This takes a long because + * all of our 4-byte values are stored locally in longs. + */ + /*package*/ static void writeIntLE(OutputStream out, long val) + throws IOException + { + if (val < 0) + throw new InternalError(); + out.write((int) val & 0xff); + out.write(((int) val >> 8) & 0xff); + out.write(((int) val >> 16) & 0xff); + out.write(((int) val >> 24) & 0xff); + } + + /* + * Write the Local File Header for this entry to the specified stream. + * + * Returns the #of bytes written. + */ + /*package*/ int writeLFH(OutputStream out) throws IOException { + if (compressionMethod < 0 || + time < 0 || + modDate < 0 || + crc < 0 || + compressedSize < 0 || + size < 0) + throw new InternalError(); + + writeIntLE(out, LOCSIG); + writeShortLE(out, mVersionToExtract); + writeShortLE(out, mGPBitFlag); + writeShortLE(out, compressionMethod); + writeShortLE(out, time); + writeShortLE(out, modDate); + writeIntLE(out, crc); + writeIntLE(out, compressedSize); + writeIntLE(out, size); + + byte[] nameBytes; + try { + nameBytes = name.getBytes("ISO-8859-1"); + } + catch (UnsupportedEncodingException uee) { + throw new InternalError(uee.getMessage()); + } + + int extraLen = 0; + if (extra != null) + extraLen = extra.length; + + writeShortLE(out, nameBytes.length); + writeShortLE(out, extraLen); + out.write(nameBytes); + if (extra != null) + out.write(extra); + + return LOCHDR + nameBytes.length + extraLen; + } + + /* + * Write the Data Descriptor for this entry to the specified stream. + * + * Returns the #of bytes written. + */ + /*package*/ int writeDD(OutputStream out) throws IOException { + writeIntLE(out, EXTSIG); + writeIntLE(out, crc); + writeIntLE(out, compressedSize); + writeIntLE(out, size); + return EXTHDR; + } + + /* + * Write the Central Directory Entry for this entry. + * + * Returns the #of bytes written. + */ + /*package*/ int writeCDE(OutputStream out) throws IOException { + writeIntLE(out, CENSIG); + writeShortLE(out, mVersionMadeBy); + writeShortLE(out, mVersionToExtract); + writeShortLE(out, mGPBitFlag); + writeShortLE(out, compressionMethod); + writeShortLE(out, time); + writeShortLE(out, modDate); + writeIntLE(out, crc); + writeIntLE(out, compressedSize); + writeIntLE(out, size); + + byte[] nameBytes = null, commentBytes = null; + try { + nameBytes = name.getBytes("ISO-8859-1"); + if (comment != null) + commentBytes = comment.getBytes("ISO-8859-1"); + } + catch (UnsupportedEncodingException uee) { + throw new InternalError(uee.getMessage()); + } + + int extraLen = 0, commentLen = 0; + if (extra != null) + extraLen = extra.length; + if (commentBytes != null) + commentLen = commentBytes.length; + + writeShortLE(out, nameBytes.length); + writeShortLE(out, extraLen); + writeShortLE(out, commentLen); + writeShortLE(out, mDiskNumberStart); + writeShortLE(out, mInternalAttrs); + writeIntLE(out, mExternalAttrs); + writeIntLE(out, mLocalHeaderRelOffset); + out.write(nameBytes); + if (extra != null) + out.write(extra); + if (commentBytes != null) + out.write(commentBytes); + + return CENHDR + nameBytes.length + extraLen + commentLen; + } + // END android-added +} diff --git a/archive/src/main/java/java/util/zip/ZipException.java b/archive/src/main/java/java/util/zip/ZipException.java new file mode 100644 index 0000000..590117b --- /dev/null +++ b/archive/src/main/java/java/util/zip/ZipException.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +import java.io.IOException; + +/** + * This runtime exception is thrown by {@code ZipFile} and {@code + * ZipInputStream} when the file or stream is not a valid ZIP file. + * + * @see ZipFile + * @see ZipInputStream + * @since Android 1.0 + */ +public class ZipException extends IOException { + + private static final long serialVersionUID = 8000196834066748623L; + + /** + * Constructs a new {@code ZipException} instance. + * + * @since Android 1.0 + */ + public ZipException() { + super(); + } + + /** + * Constructs a new {@code ZipException} instance with the specified + * message. + * + * @param detailMessage + * the detail message for the exception. + * @since Android 1.0 + */ + public ZipException(String detailMessage) { + super(detailMessage); + } + +} diff --git a/archive/src/main/java/java/util/zip/ZipFile.java b/archive/src/main/java/java/util/zip/ZipFile.java new file mode 100644 index 0000000..f1415d9 --- /dev/null +++ b/archive/src/main/java/java/util/zip/ZipFile.java @@ -0,0 +1,433 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.NoSuchElementException; + +/** + * This class provides random read access to a <i>ZIP-archive</i> file. + * <p> + * While {@code ZipInputStream} provides stream based read access to a + * <i>ZIP-archive</i>, this class implements more efficient (file based) access + * and makes use of the <i>central directory</i> within a <i>ZIP-archive</i>. + * </p> + * <p> + * Use {@code ZipOutputStream} if you want to create an archive. + * </p> + * <p> + * A temporary ZIP file can be marked for automatic deletion upon closing it. + * </p> + * + * @see ZipEntry + * @see ZipOutputStream + * @since Android 1.0 + */ +public class ZipFile implements ZipConstants { + + String fileName; + + File fileToDeleteOnClose; + + /** + * Open zip file for read. + * + * @since Android 1.0 + */ + public static final int OPEN_READ = 1; + + /** + * Delete zip file when closed. + * + * @since Android 1.0 + */ + public static final int OPEN_DELETE = 4; + + /** + * Constructs a new {@code ZipFile} with the specified file. + * + * @param file + * the file to read from. + * @throws ZipException + * if a ZIP error occurs. + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + public ZipFile(File file) throws ZipException, IOException { + this(file, OPEN_READ); + } + + /** + * Opens a file as <i>ZIP-archive</i>. "mode" must be {@code OPEN_READ} or + * {@code OPEN_DELETE} . The latter sets the "delete on exit" flag through a + * file. + * + * @param file + * the ZIP file to read. + * @param mode + * the mode of the file open operation. + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + public ZipFile(File file, int mode) throws IOException { + if (mode == (OPEN_READ | OPEN_DELETE)) + fileToDeleteOnClose = file; // file.deleteOnExit(); + else if (mode != OPEN_READ) + throw new IllegalArgumentException("invalid mode"); + + fileName = file.getPath(); + mRaf = new RandomAccessFile(fileName, "r"); + + mEntryList = new ArrayList<ZipEntry>(); + + readCentralDir(); + + /* + * No LinkedHashMap yet, so optimize lookup-by-name by creating + * a parallel data structure. + */ + mFastLookup = new HashMap<String, ZipEntry>(mEntryList.size() * 2); + for (int i = 0; i < mEntryList.size(); i++) { + ZipEntry entry = mEntryList.get(i); + + mFastLookup.put(entry.getName(), entry); + } + } + + /** + * Opens a ZIP archived file. + * + * @param name + * the name of the ZIP file. + * @throws IOException + * if an IOException occurs. + * @since Android 1.0 + */ + public ZipFile(String name) throws IOException { + this(new File(name), OPEN_READ); + } + + @Override + protected void finalize() throws IOException { + close(); + } + + /** + * Closes this ZIP file. + * + * @throws IOException + * if an IOException occurs. + * @since Android 1.0 + */ + public void close() throws IOException { + RandomAccessFile raf = mRaf; + + if (raf != null) { // Only close initialized instances + synchronized(raf) { + mRaf = null; + raf.close(); + } + if (fileToDeleteOnClose != null) { + AccessController.doPrivileged(new PrivilegedAction<Object>() { + public Object run() { + new File(fileName).delete(); + return null; + } + }); + // fileToDeleteOnClose.delete(); + fileToDeleteOnClose = null; + } + } + } + + /** + * Returns an enumeration of the entries. The entries are listed in the + * order in which they appear in the ZIP archive. + * + * @return the enumeration of the entries. + * @since Android 1.0 + */ + public Enumeration<? extends ZipEntry> entries() { + return new Enumeration<ZipEntry>() { + private int i = 0; + + public boolean hasMoreElements() { + if (mRaf == null) throw new IllegalStateException("Zip File closed."); + return i < mEntryList.size(); + } + + public ZipEntry nextElement() { + if (mRaf == null) throw new IllegalStateException("Zip File closed."); + if (i >= mEntryList.size()) + throw new NoSuchElementException(); + return (ZipEntry) mEntryList.get(i++); + } + }; + } + + /** + * Gets the ZIP entry with the specified name from this {@code ZipFile}. + * + * @param entryName + * the name of the entry in the ZIP file. + * @return a {@code ZipEntry} or {@code null} if the entry name does not + * exist in the ZIP file. + * @since Android 1.0 + */ + public ZipEntry getEntry(String entryName) { + if (entryName != null) { + ZipEntry ze = mFastLookup.get(entryName); + if (ze == null) ze = mFastLookup.get(entryName + "/"); + return ze; + } + throw new NullPointerException(); + } + + /** + * Returns an input stream on the data of the specified {@code ZipEntry}. + * + * @param entry + * the ZipEntry. + * @return an input stream of the data contained in the {@code ZipEntry}. + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + public InputStream getInputStream(ZipEntry entry) throws IOException { + /* + * Make sure this ZipEntry is in this Zip file. We run it through + * the name lookup. + */ + entry = getEntry(entry.getName()); + if (entry == null) + return null; + + /* + * Create a ZipInputStream at the right part of the file. + */ + RandomAccessFile raf = mRaf; + if (raf != null) { + synchronized (raf) { + // Unfortunately we don't know the entry data's start position. + // All we have is the position of the entry's local header. + // At position 28 we find the length of the extra data. + // In some cases this length differs from the one coming in + // the central header!!! + RAFStream rafstrm = new RAFStream(raf, entry.mLocalHeaderRelOffset + 28); + int localExtraLenOrWhatever = ler.readShortLE(rafstrm); + // Now we need to skip the name + // and this "extra" data or whatever it is: + rafstrm.skip(entry.nameLen + localExtraLenOrWhatever); + rafstrm.mLength = rafstrm.mOffset + entry.compressedSize; + if (entry.compressionMethod == ZipEntry.DEFLATED) { + return new InflaterInputStream(rafstrm, new Inflater(true)); + } else { + return rafstrm; + } + } + } + throw new IllegalStateException("Zip File closed"); + } + + /** + * Gets the file name of this {@code ZipFile}. + * + * @return the file name of this {@code ZipFile}. + * @since Android 1.0 + */ + public String getName() { + return fileName; + } + + /** + * Returns the number of {@code ZipEntries} in this {@code ZipFile}. + * + * @return the number of entries in this file. + * @since Android 1.0 + */ + public int size() { + return mEntryList.size(); + } + + /* + * Find the central directory and read the contents. + * + * The central directory can be followed by a variable-length comment + * field, so we have to scan through it backwards. The comment is at + * most 64K, plus we have 18 bytes for the end-of-central-dir stuff + * itself, plus apparently sometimes people throw random junk on the end + * just for the fun of it. + * + * This is all a little wobbly. If the wrong value ends up in the EOCD + * area, we're hosed. This appears to be the way that everbody handles + * it though, so we're in pretty good company if this fails. + */ + private void readCentralDir() throws IOException { + long scanOffset, stopOffset; + long sig; + + /* + * Scan back, looking for the End Of Central Directory field. If + * the archive doesn't have a comment, we'll hit it on the first + * try. + * + * No need to synchronize mRaf here -- we only do this when we + * first open the Zip file. + */ + scanOffset = mRaf.length() - ENDHDR; + if (scanOffset < 0) + throw new ZipException("too short to be Zip"); + + stopOffset = scanOffset - 65536; + if (stopOffset < 0) + stopOffset = 0; + + while (true) { + mRaf.seek(scanOffset); + if (ZipEntry.readIntLE(mRaf) == 101010256L) + break; + + //System.out.println("not found at " + scanOffset); + scanOffset--; + if (scanOffset < stopOffset) + throw new ZipException("EOCD not found; not a Zip archive?"); + } + + /* + * Found it, read the EOCD. + * + * For performance we want to use buffered I/O when reading the + * file. We wrap a buffered stream around the random-access file + * object. If we just read from the RandomAccessFile we'll be + * doing a read() system call every time. + */ + RAFStream rafs = new RAFStream(mRaf, mRaf.getFilePointer()); + BufferedInputStream bin = new BufferedInputStream(rafs, ENDHDR); + int diskNumber, diskWithCentralDir, numEntries, totalNumEntries; + //long centralDirSize; + long centralDirOffset; + //int commentLen; + + diskNumber = ler.readShortLE(bin); + diskWithCentralDir = ler.readShortLE(bin); + numEntries = ler.readShortLE(bin); + totalNumEntries = ler.readShortLE(bin); + /*centralDirSize =*/ ler.readIntLE(bin); + centralDirOffset = ler.readIntLE(bin); + /*commentLen =*/ ler.readShortLE(bin); + + if (numEntries != totalNumEntries || + diskNumber != 0 || + diskWithCentralDir != 0) + throw new ZipException("spanned archives not supported"); + + /* + * Seek to the first CDE and read all entries. + */ + rafs = new RAFStream(mRaf, centralDirOffset); + bin = new BufferedInputStream(rafs, 4096); + for (int i = 0; i < numEntries; i++) { + ZipEntry newEntry; + + newEntry = new ZipEntry(ler, bin); + mEntryList.add(newEntry); + } + } + + /* + * Local data items. + */ + private RandomAccessFile mRaf; + + ZipEntry.LittleEndianReader ler = new ZipEntry.LittleEndianReader(); + + /* + * What we really want here is a LinkedHashMap, because we want fast + * lookups by name, but we want to preserve the ordering of the archive + * entries. Unfortunately we don't yet have a LinkedHashMap + * implementation. + */ + private ArrayList<ZipEntry> mEntryList; + private HashMap<String, ZipEntry> mFastLookup; + + /* + * Wrap a stream around a RandomAccessFile. The RandomAccessFile + * is shared among all streams returned by getInputStream(), so we + * have to synchronize access to it. (We can optimize this by + * adding buffering here to reduce collisions.) + * + * We could support mark/reset, but we don't currently need them. + */ + static class RAFStream extends InputStream { + public RAFStream(RandomAccessFile raf, long pos) throws IOException { + mSharedRaf = raf; + mOffset = pos; + mLength = raf.length(); + } + + @Override + public int available() throws IOException { + return (mOffset < mLength ? 1 : 0); + } + + public int read() throws IOException { + if (read(singleByteBuf, 0, 1) == 1) return singleByteBuf[0] & 0XFF; + else return -1; + } + + public int read(byte[] b, int off, int len) throws IOException { + int count; + synchronized (mSharedRaf) { + mSharedRaf.seek(mOffset); + if (mOffset + len > mLength) len = (int) (mLength - mOffset); + count = mSharedRaf.read(b, off, len); + if (count > 0) { + mOffset += count; + } + else return -1; + } + return count; + } + + @Override + public long skip(long n) throws IOException { + if (mOffset + n > mLength) + n = mLength - mOffset; + mOffset += n; + return n; + } + + RandomAccessFile mSharedRaf; + long mOffset; + long mLength; + private byte[] singleByteBuf = new byte[1]; + } +} diff --git a/archive/src/main/java/java/util/zip/ZipInputStream.java b/archive/src/main/java/java/util/zip/ZipInputStream.java new file mode 100644 index 0000000..262fa3f --- /dev/null +++ b/archive/src/main/java/java/util/zip/ZipInputStream.java @@ -0,0 +1,414 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; + +import org.apache.harmony.archive.internal.nls.Messages; +import org.apache.harmony.luni.util.Util; + +/** + * This class provides an implementation of {@code FilterInputStream} that + * uncompresses data from a <i>ZIP-archive</i> input stream. + * <p> + * A <i>ZIP-archive</i> is a collection of compressed (or uncompressed) files - + * the so called ZIP entries. Therefore when reading from a {@code + * ZipInputStream} first the entry's attributes will be retrieved with {@code + * getNextEntry} before its data is read. + * </p> + * <p> + * While {@code InflaterInputStream} can read a compressed <i>ZIP-archive</i> + * entry, this extension can read uncompressed entries as well. + * </p> + * <p> + * Use {@code ZipFile} if you can access the archive as a file directly. + * </p> + * + * @see ZipEntry + * @see ZipFile + * @since Android 1.0 + */ +public class ZipInputStream extends InflaterInputStream implements ZipConstants { + static final int DEFLATED = 8; + + static final int STORED = 0; + + static final int ZIPDataDescriptorFlag = 8; + + static final int ZIPLocalHeaderVersionNeeded = 20; + + // BEGI android-removed + // private boolean zipClosed = false; + // END android-removed + + private boolean entriesEnd = false; + + private boolean hasDD = false; + + private int entryIn = 0; + + private int inRead, lastRead = 0; + + ZipEntry currentEntry; + + private final byte[] hdrBuf = new byte[LOCHDR - LOCVER]; + + private final CRC32 crc = new CRC32(); + + private byte[] nameBuf = new byte[256]; + + private char[] charBuf = new char[256]; + + /** + * Constructs a new {@code ZipInputStream} from the specified input stream. + * + * @param stream + * the input stream to representing a ZIP archive. + * @since Android 1.0 + */ + public ZipInputStream(InputStream stream) { + super(new PushbackInputStream(stream, BUF_SIZE), new Inflater(true)); + if (stream == null) { + throw new NullPointerException(); + } + } + + /** + * Closes this {@code ZipInputStream}. + * + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + @Override + public void close() throws IOException { + // BEGIN android-changed + if (closed != true) { + closeEntry(); // Close the current entry + super.close(); + } + // END android-changed + } + + /** + * Closes the current ZIP entry and positions to read the next entry. + * + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + public void closeEntry() throws IOException { + // BEGIN android-changed + if (closed) { + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + // END android-changed + if (currentEntry == null) { + return; + } + if (currentEntry instanceof java.util.jar.JarEntry) { + Attributes temp = ((JarEntry) currentEntry).getAttributes(); + if (temp != null && temp.containsKey("hidden")) { //$NON-NLS-1$ + return; + } + } + // Ensure all entry bytes are read + skip(Long.MAX_VALUE); + int inB, out; + if (currentEntry.compressionMethod == DEFLATED) { + inB = inf.getTotalIn(); + out = inf.getTotalOut(); + } else { + inB = inRead; + out = inRead; + } + int diff = 0; + // Pushback any required bytes + if ((diff = entryIn - inB) != 0) { + ((PushbackInputStream) in).unread(buf, len - diff, diff); + } + + if (hasDD) { + in.read(hdrBuf, 0, EXTHDR); + if (getLong(hdrBuf, 0) != EXTSIG) { + throw new ZipException(Messages.getString("archive.1F")); //$NON-NLS-1$ + } + currentEntry.crc = getLong(hdrBuf, EXTCRC); + currentEntry.compressedSize = getLong(hdrBuf, EXTSIZ); + currentEntry.size = getLong(hdrBuf, EXTLEN); + } + if (currentEntry.crc != crc.getValue()) { + throw new ZipException(Messages.getString("archive.20")); //$NON-NLS-1$ + } + if (currentEntry.compressedSize != inB || currentEntry.size != out) { + throw new ZipException(Messages.getString("archive.21")); //$NON-NLS-1$ + } + + inf.reset(); + lastRead = inRead = entryIn = len = 0; + crc.reset(); + currentEntry = null; + } + + /** + * Reads the next entry from this {@code ZipInputStream}. + * + * @return the next {@code ZipEntry} contained in the input stream. + * @throws IOException + * if the stream is not positioned at the beginning of an entry + * or if an other {@code IOException} occurs. + * @see ZipEntry + * @since Android 1.0 + */ + public ZipEntry getNextEntry() throws IOException { + if (currentEntry != null) { + closeEntry(); + } + if (entriesEnd) { + return null; + } + + int x = 0, count = 0; + while (count != 4) { + count += x = in.read(hdrBuf, count, 4 - count); + if (x == -1) { + return null; + } + } + long hdr = getLong(hdrBuf, 0); + if (hdr == CENSIG) { + entriesEnd = true; + return null; + } + if (hdr != LOCSIG) { + return null; + } + + // Read the local header + count = 0; + while (count != (LOCHDR - LOCVER)) { + count += x = in.read(hdrBuf, count, (LOCHDR - LOCVER) - count); + if (x == -1) { + throw new EOFException(); + } + } + int version = getShort(hdrBuf, 0) & 0xff; + if (version > ZIPLocalHeaderVersionNeeded) { + throw new ZipException(Messages.getString("archive.22")); //$NON-NLS-1$ + } + int flags = getShort(hdrBuf, LOCFLG - LOCVER); + hasDD = ((flags & ZIPDataDescriptorFlag) == ZIPDataDescriptorFlag); + int cetime = getShort(hdrBuf, LOCTIM - LOCVER); + int cemodDate = getShort(hdrBuf, LOCTIM - LOCVER + 2); + int cecompressionMethod = getShort(hdrBuf, LOCHOW - LOCVER); + long cecrc = 0, cecompressedSize = 0, cesize = -1; + if (!hasDD) { + cecrc = getLong(hdrBuf, LOCCRC - LOCVER); + cecompressedSize = getLong(hdrBuf, LOCSIZ - LOCVER); + cesize = getLong(hdrBuf, LOCLEN - LOCVER); + } + int flen = getShort(hdrBuf, LOCNAM - LOCVER); + if (flen == 0) { + throw new ZipException(Messages.getString("archive.23")); //$NON-NLS-1$ + } + int elen = getShort(hdrBuf, LOCEXT - LOCVER); + + count = 0; + if (flen > nameBuf.length) { + nameBuf = new byte[flen]; + charBuf = new char[flen]; + } + while (count != flen) { + count += x = in.read(nameBuf, count, flen - count); + if (x == -1) { + throw new EOFException(); + } + } + currentEntry = createZipEntry(Util.convertUTF8WithBuf(nameBuf, charBuf, + 0, flen)); + currentEntry.time = cetime; + currentEntry.modDate = cemodDate; + currentEntry.setMethod(cecompressionMethod); + if (cesize != -1) { + currentEntry.setCrc(cecrc); + currentEntry.setSize(cesize); + currentEntry.setCompressedSize(cecompressedSize); + } + if (elen > 0) { + count = 0; + byte[] e = new byte[elen]; + while (count != elen) { + count += x = in.read(e, count, elen - count); + if (x == -1) { + throw new EOFException(); + } + } + currentEntry.setExtra(e); + } + // BEGIN android-added + eof = false; + // END android-added + return currentEntry; + } + + /* Read 4 bytes from the buffer and store it as an int */ + + @Override + public int read(byte[] buffer, int start, int length) throws IOException { + // BEGIN android-changed + if (closed) { + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + // END android-changed + if (inf.finished() || currentEntry == null) { + return -1; + } + // avoid int overflow, check null buffer + if (start <= buffer.length && length >= 0 && start >= 0 + && buffer.length - start >= length) { + if (currentEntry.compressionMethod == STORED) { + int csize = (int) currentEntry.size; + if (inRead >= csize) { + // BEGIN android-added + eof = true; + // END android-added + return -1; + } + if (lastRead >= len) { + lastRead = 0; + if ((len = in.read(buf)) == -1) { + // BEGIN android-added + eof = true; + // END android-added + return -1; + } + entryIn += len; + } + // BEGIN android-changed + int toRead = length > (len - lastRead) ? len - lastRead : length; + // END android-changed + if ((csize - inRead) < toRead) { + toRead = csize - inRead; + } + System.arraycopy(buf, lastRead, buffer, start, toRead); + lastRead += toRead; + inRead += toRead; + crc.update(buffer, start, toRead); + return toRead; + } + if (inf.needsInput()) { + fill(); + if (len > 0) { + entryIn += len; + } + } + int read = 0; + try { + read = inf.inflate(buffer, start, length); + } catch (DataFormatException e) { + throw new ZipException(e.getMessage()); + } + if (read == 0 && inf.finished()) { + return -1; + } + crc.update(buffer, start, read); + return read; + } + throw new ArrayIndexOutOfBoundsException(); + } + + /** + * Skips up to the specified number of bytes in the current ZIP entry. + * + * @param value + * the number of bytes to skip. + * @return the number of bytes skipped. + * @throws IOException + * if an {@code IOException} occurs. + * @since Android 1.0 + */ + @Override + public long skip(long value) throws IOException { + if (value >= 0) { + long skipped = 0; + byte[] b = new byte[1024]; + while (skipped != value) { + long rem = value - skipped; + int x = read(b, 0, (int) (b.length > rem ? rem : b.length)); + if (x == -1) { + return skipped; + } + skipped += x; + } + return skipped; + } + throw new IllegalArgumentException(); +} + + /** + * Returns 0 if the {@code EOF} has been reached, otherwise returns 1. + * + * @return 0 after {@code EOF} of current entry, 1 otherwise. + * @throws IOException + * if an IOException occurs. + * @since Android 1.0 + */ + @Override + public int available() throws IOException { + // BEGIN android-changed + if (closed) { + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + if (currentEntry == null) { + return 1; + } + return super.available(); + // END android-changed + } + + /** + * creates a {@link ZipEntry } with the given name. + * + * @param name + * the name of the entry. + * @return the created {@code ZipEntry}. + * @since Android 1.0 + */ + protected ZipEntry createZipEntry(String name) { + return new ZipEntry(name); + } + + private int getShort(byte[] buffer, int off) { + return (buffer[off] & 0xFF) | ((buffer[off + 1] & 0xFF) << 8); + } + + private long getLong(byte[] buffer, int off) { + long l = 0; + l |= (buffer[off] & 0xFF); + l |= (buffer[off + 1] & 0xFF) << 8; + l |= (buffer[off + 2] & 0xFF) << 16; + l |= ((long) (buffer[off + 3] & 0xFF)) << 24; + return l; + } +} diff --git a/archive/src/main/java/java/util/zip/ZipOutputStream.java b/archive/src/main/java/java/util/zip/ZipOutputStream.java new file mode 100644 index 0000000..4ddf643 --- /dev/null +++ b/archive/src/main/java/java/util/zip/ZipOutputStream.java @@ -0,0 +1,456 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package java.util.zip; + + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Vector; + +import org.apache.harmony.archive.internal.nls.Messages; + +/** + * This class provides an implementation of {@code FilterOutputStream} that + * compresses data entries into a <i>ZIP-archive</i> output stream. + * <p> + * {@code ZipOutputStream} is used to write {@code ZipEntries} to the underlying + * stream. Output from {@code ZipOutputStream} conforms to the {@code ZipFile} + * file format. + * </p> + * <p> + * While {@code DeflaterOutputStream} can write a compressed <i>ZIP-archive</i> + * entry, this extension can write uncompressed entries as well. In this case + * special rules apply, for this purpose refer to the <a + * href="http://www.pkware.com/documents/casestudies/APPNOTE.TXT">file format + * specification</a>. + * </p> + * + * @see ZipEntry + * @see ZipFile + * @since Android 1.0 + */ +public class ZipOutputStream extends DeflaterOutputStream implements + ZipConstants { + + /** + * Indicates deflated entries. + * + * @since Android 1.0 + */ + public static final int DEFLATED = 8; + + /** + * Indicates uncompressed entries. + * + * @since Android 1.0 + */ + public static final int STORED = 0; + + static final int ZIPDataDescriptorFlag = 8; + + static final int ZIPLocalHeaderVersionNeeded = 20; + + private String comment; + + private final Vector<String> entries = new Vector<String>(); + + private int compressMethod = DEFLATED; + + private int compressLevel = Deflater.DEFAULT_COMPRESSION; + + private ByteArrayOutputStream cDir = new ByteArrayOutputStream(); + + private ZipEntry currentEntry; + + private final CRC32 crc = new CRC32(); + + private int offset = 0, curOffset = 0, nameLength; + + private byte[] nameBytes; + + /** + * Constructs a new {@code ZipOutputStream} with the specified output + * stream. + * + * @param p1 + * the {@code OutputStream} to write the data to. + * @since Android 1.0 + */ + public ZipOutputStream(OutputStream p1) { + super(p1, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); + } + + /** + * Closes the current {@code ZipEntry}, if any, and the underlying output + * stream. If the stream is already closed this method does nothing. + * + * @throws IOException + * If an error occurs closing the stream. + * @since Android 1.0 + */ + @Override + public void close() throws IOException { + if (out != null) { + finish(); + out.close(); + out = null; + } + } + + /** + * Closes the current {@code ZipEntry}. Any entry terminal data is written + * to the underlying stream. + * + * @throws IOException + * If an error occurs closing the entry. + * @since Android 1.0 + */ + public void closeEntry() throws IOException { + if (cDir == null) { + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + if (currentEntry == null) { + return; + } + if (currentEntry.getMethod() == DEFLATED) { + super.finish(); + } + + // Verify values for STORED types + if (currentEntry.getMethod() == STORED) { + if (crc.getValue() != currentEntry.crc) { + throw new ZipException(Messages.getString("archive.20")); //$NON-NLS-1$ + } + if (currentEntry.size != crc.tbytes) { + throw new ZipException(Messages.getString("archive.21")); //$NON-NLS-1$ + } + } + curOffset = LOCHDR; + + // Write the DataDescriptor + if (currentEntry.getMethod() != STORED) { + curOffset += EXTHDR; + writeLong(out, EXTSIG); + writeLong(out, currentEntry.crc = crc.getValue()); + writeLong(out, currentEntry.compressedSize = def.getTotalOut()); + writeLong(out, currentEntry.size = def.getTotalIn()); + } + // Update the CentralDirectory + writeLong(cDir, CENSIG); + writeShort(cDir, ZIPLocalHeaderVersionNeeded); // Version created + writeShort(cDir, ZIPLocalHeaderVersionNeeded); // Version to extract + writeShort(cDir, currentEntry.getMethod() == STORED ? 0 + : ZIPDataDescriptorFlag); + writeShort(cDir, currentEntry.getMethod()); + writeShort(cDir, currentEntry.time); + writeShort(cDir, currentEntry.modDate); + writeLong(cDir, crc.getValue()); + if (currentEntry.getMethod() == DEFLATED) { + curOffset += writeLong(cDir, def.getTotalOut()); + writeLong(cDir, def.getTotalIn()); + } else { + curOffset += writeLong(cDir, crc.tbytes); + writeLong(cDir, crc.tbytes); + } + curOffset += writeShort(cDir, nameLength); + if (currentEntry.extra != null) { + curOffset += writeShort(cDir, currentEntry.extra.length); + } else { + writeShort(cDir, 0); + } + String c; + if ((c = currentEntry.getComment()) != null) { + writeShort(cDir, c.length()); + } else { + writeShort(cDir, 0); + } + writeShort(cDir, 0); // Disk Start + writeShort(cDir, 0); // Internal File Attributes + writeLong(cDir, 0); // External File Attributes + writeLong(cDir, offset); + cDir.write(nameBytes); + nameBytes = null; + if (currentEntry.extra != null) { + cDir.write(currentEntry.extra); + } + offset += curOffset; + if (c != null) { + cDir.write(c.getBytes()); + } + currentEntry = null; + crc.reset(); + def.reset(); + done = false; + } + + /** + * Indicates that all entries have been written to the stream. Any terminal + * information is written to the underlying stream. + * + * @throws IOException + * if an error occurs while terminating the stream. + * @since Android 1.0 + */ + @Override + public void finish() throws IOException { + if (out == null) { + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + if (cDir == null) { + return; + } + if (entries.size() == 0) { + throw new ZipException(Messages.getString("archive.28")); //$NON-NLS-1$; + } + if (currentEntry != null) { + closeEntry(); + } + int cdirSize = cDir.size(); + // Write Central Dir End + writeLong(cDir, ENDSIG); + writeShort(cDir, 0); // Disk Number + writeShort(cDir, 0); // Start Disk + writeShort(cDir, entries.size()); // Number of entries + writeShort(cDir, entries.size()); // Number of entries + writeLong(cDir, cdirSize); // Size of central dir + writeLong(cDir, offset); // Offset of central dir + if (comment != null) { + writeShort(cDir, comment.length()); + cDir.write(comment.getBytes()); + } else { + writeShort(cDir, 0); + } + // Write the central dir + out.write(cDir.toByteArray()); + cDir = null; + + } + + /** + * Writes entry information to the underlying stream. Data associated with + * the entry can then be written using {@code write()}. After data is + * written {@code closeEntry()} must be called to complete the writing of + * the entry to the underlying stream. + * + * @param ze + * the {@code ZipEntry} to store. + * @throws IOException + * If an error occurs storing the entry. + * @see #write + * @since Android 1.0 + */ + public void putNextEntry(ZipEntry ze) throws java.io.IOException { + if (currentEntry != null) { + closeEntry(); + } + if (ze.getMethod() == STORED + || (compressMethod == STORED && ze.getMethod() == -1)) { + if (ze.crc == -1) { + /* [MSG "archive.20", "Crc mismatch"] */ + throw new ZipException(Messages.getString("archive.20")); //$NON-NLS-1$ + } + if (ze.size == -1 && ze.compressedSize == -1) { + /* [MSG "archive.21", "Size mismatch"] */ + throw new ZipException(Messages.getString("archive.21")); //$NON-NLS-1$ + } + if (ze.size != ze.compressedSize && ze.compressedSize != -1 + && ze.size != -1) { + /* [MSG "archive.21", "Size mismatch"] */ + throw new ZipException(Messages.getString("archive.21")); //$NON-NLS-1$ + } + } + /* [MSG "archive.1E", "Stream is closed"] */ + if (cDir == null) { + throw new IOException(Messages.getString("archive.1E")); //$NON-NLS-1$ + } + if (entries.contains(ze.name)) { + /* [MSG "archive.29", "Entry already exists: {0}"] */ + throw new ZipException(Messages.getString("archive.29", ze.name)); //$NON-NLS-1$ + } + nameLength = utf8Count(ze.name); + if (nameLength > 0xffff) { + /* [MSG "archive.2A", "Name too long: {0}"] */ + throw new IllegalArgumentException(Messages.getString("archive.2A", ze.name)); //$NON-NLS-1$ + } + + def.setLevel(compressLevel); + currentEntry = ze; + entries.add(currentEntry.name); + if (currentEntry.getMethod() == -1) { + currentEntry.setMethod(compressMethod); + } + writeLong(out, LOCSIG); // Entry header + writeShort(out, ZIPLocalHeaderVersionNeeded); // Extraction version + writeShort(out, currentEntry.getMethod() == STORED ? 0 + : ZIPDataDescriptorFlag); + writeShort(out, currentEntry.getMethod()); + if (currentEntry.getTime() == -1) { + currentEntry.setTime(System.currentTimeMillis()); + } + writeShort(out, currentEntry.time); + writeShort(out, currentEntry.modDate); + + if (currentEntry.getMethod() == STORED) { + if (currentEntry.size == -1) { + currentEntry.size = currentEntry.compressedSize; + } else if (currentEntry.compressedSize == -1) { + currentEntry.compressedSize = currentEntry.size; + } + writeLong(out, currentEntry.crc); + writeLong(out, currentEntry.size); + writeLong(out, currentEntry.size); + } else { + writeLong(out, 0); + writeLong(out, 0); + writeLong(out, 0); + } + writeShort(out, nameLength); + if (currentEntry.extra != null) { + writeShort(out, currentEntry.extra.length); + } else { + writeShort(out, 0); + } + nameBytes = toUTF8Bytes(currentEntry.name, nameLength); + out.write(nameBytes); + if (currentEntry.extra != null) { + out.write(currentEntry.extra); + } + } + + /** + * Sets the {@code ZipFile} comment associated with the file being written. + * + * @param comment + * the comment associated with the file. + * @since Android 1.0 + */ + public void setComment(String comment) { + if (comment.length() > 0xFFFF) { + throw new IllegalArgumentException(Messages.getString("archive.2B")); //$NON-NLS-1$ + } + this.comment = comment; + } + + /** + * Sets the compression level to be used for writing entry data. This level + * may be set on a per entry basis. The level must have a value between -1 + * and 8 according to the {@code Deflater} compression level bounds. + * + * @param level + * the compression level (ranging from -1 to 8). + * @see Deflater + * @since Android 1.0 + */ + public void setLevel(int level) { + if (level < Deflater.DEFAULT_COMPRESSION + || level > Deflater.BEST_COMPRESSION) { + throw new IllegalArgumentException(); + } + compressLevel = level; + } + + /** + * Sets the compression method to be used when compressing entry data. + * method must be one of {@code STORED} (for no compression) or {@code + * DEFLATED}. + * + * @param method + * the compression method to use. + * @since Android 1.0 + */ + public void setMethod(int method) { + if (method != STORED && method != DEFLATED) { + throw new IllegalArgumentException(); + } + compressMethod = method; + + } + + private long writeLong(OutputStream os, long i) throws java.io.IOException { + // Write out the long value as an unsigned int + os.write((int) (i & 0xFF)); + os.write((int) (i >> 8) & 0xFF); + os.write((int) (i >> 16) & 0xFF); + os.write((int) (i >> 24) & 0xFF); + return i; + } + + private int writeShort(OutputStream os, int i) throws java.io.IOException { + os.write(i & 0xFF); + os.write((i >> 8) & 0xFF); + return i; + + } + + @Override + public void write(byte[] buffer, int off, int nbytes) + throws java.io.IOException { + // avoid int overflow, check null buf + if ((off > buffer.length) || (nbytes < 0) || (off < 0) + || (buffer.length - off < nbytes)) { + throw new IndexOutOfBoundsException(); + } + + if (currentEntry == null) { + /* [MSG "archive.2C", "No active entry"] */ + throw new ZipException(Messages.getString("archive.2C")); //$NON-NLS-1$ + } + + if (currentEntry.getMethod() == STORED) { + out.write(buffer, off, nbytes); + } else { + super.write(buffer, off, nbytes); + } + crc.update(buffer, off, nbytes); + } + + static int utf8Count(String value) { + int total = 0; + for (int i = value.length(); --i >= 0;) { + char ch = value.charAt(i); + if (ch < 0x80) { + total++; + } else if (ch < 0x800) { + total += 2; + } else { + total += 3; + } + } + return total; + } + + static byte[] toUTF8Bytes(String value, int length) { + byte[] result = new byte[length]; + int pos = result.length; + for (int i = value.length(); --i >= 0;) { + char ch = value.charAt(i); + if (ch < 0x80) { + result[--pos] = (byte) ch; + } else if (ch < 0x800) { + result[--pos] = (byte) (0x80 | (ch & 0x3f)); + result[--pos] = (byte) (0xc0 | (ch >> 6)); + } else { + result[--pos] = (byte) (0x80 | (ch & 0x3f)); + result[--pos] = (byte) (0x80 | ((ch >> 6) & 0x3f)); + result[--pos] = (byte) (0xe0 | (ch >> 12)); + } + } + return result; + } +} diff --git a/archive/src/main/java/java/util/zip/package.html b/archive/src/main/java/java/util/zip/package.html new file mode 100644 index 0000000..2a09bf1 --- /dev/null +++ b/archive/src/main/java/java/util/zip/package.html @@ -0,0 +1,9 @@ +<html> + <body> + <p> + This package contains classes for compressing and decompressing data in + ZIP and GZIP file formats. + </p> + @since Android 1.0 + </body> +</html> diff --git a/archive/src/main/java/org/apache/harmony/archive/internal/nls/Messages.java b/archive/src/main/java/org/apache/harmony/archive/internal/nls/Messages.java new file mode 100644 index 0000000..764f34d --- /dev/null +++ b/archive/src/main/java/org/apache/harmony/archive/internal/nls/Messages.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * THE FILE HAS BEEN AUTOGENERATED BY MSGTOOL TOOL. + * All changes made to this file manually will be overwritten + * if this tool runs again. Better make changes in the template file. + */ + +// BEGIN android-note +// Redundant code has been removed and is now called from MsgHelp. +// END android-note + +package org.apache.harmony.archive.internal.nls; + + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Locale; +import java.util.MissingResourceException; +import java.util.ResourceBundle; + +// BEGIN android-changed +import org.apache.harmony.luni.util.MsgHelp; +// END android-changed + +/** + * This class retrieves strings from a resource bundle and returns them, + * formatting them with MessageFormat when required. + * <p> + * It is used by the system classes to provide national language support, by + * looking up messages in the <code> + * org.apache.harmony.archive.internal.nls.messages + * </code> + * resource bundle. Note that if this file is not available, or an invalid key + * is looked up, or resource bundle support is not available, the key itself + * will be returned as the associated message. This means that the <em>KEY</em> + * should a reasonable human-readable (english) string. + * + */ +public class Messages { + + // BEGIN android-changed + private static final String sResource = + "org.apache.harmony.archive.internal.nls.messages"; + // END android-changed + + /** + * Retrieves a message which has no arguments. + * + * @param msg + * String the key to look up. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg) { + // BEGIN android-changed + return MsgHelp.getString(sResource, msg); + // END android-changed + } + + /** + * Retrieves a message which takes 1 argument. + * + * @param msg + * String the key to look up. + * @param arg + * Object the object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg) { + return getString(msg, new Object[] { arg }); + } + + /** + * Retrieves a message which takes 1 integer argument. + * + * @param msg + * String the key to look up. + * @param arg + * int the integer to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, int arg) { + return getString(msg, new Object[] { Integer.toString(arg) }); + } + + /** + * Retrieves a message which takes 1 character argument. + * + * @param msg + * String the key to look up. + * @param arg + * char the character to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, char arg) { + return getString(msg, new Object[] { String.valueOf(arg) }); + } + + /** + * Retrieves a message which takes 2 arguments. + * + * @param msg + * String the key to look up. + * @param arg1 + * Object an object to insert in the formatted output. + * @param arg2 + * Object another object to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object arg1, Object arg2) { + return getString(msg, new Object[] { arg1, arg2 }); + } + + /** + * Retrieves a message which takes several arguments. + * + * @param msg + * String the key to look up. + * @param args + * Object[] the objects to insert in the formatted output. + * @return String the message for that key in the system message bundle. + */ + static public String getString(String msg, Object[] args) { + // BEGIN android-changed + return MsgHelp.getString(sResource, msg, args); + // END android-changed + } + + // BEGIN android-note + // Duplicate code was dropped in favor of using MsgHelp. + // END android-note +} diff --git a/archive/src/main/java/org/apache/harmony/archive/internal/nls/messages.properties b/archive/src/main/java/org/apache/harmony/archive/internal/nls/messages.properties new file mode 100644 index 0000000..1ae5c5e --- /dev/null +++ b/archive/src/main/java/org/apache/harmony/archive/internal/nls/messages.properties @@ -0,0 +1,67 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# messages for EN locale +archive.00=Canonical encodings have been incorrectly modified +archive.01=Encoding cannot be less than zero +archive.02=End of buffer read whilst trying to decode codec +archive.03=End of buffer read whilst trying to decode codec +archive.04=ADef and BDef should never both be true +archive.05=Invalid codec encoding byte ({0}) found +archive.06=Something has gone wrong during parsing references +archive.07=Bad header +archive.08=Invalid segment major version +archive.09=Invalid segment minor version +archive.0A=There are attribute flags, and I don't know what to do with them +archive.0B=Not yet implemented +archive.0C=No idea what the adc is for yet +archive.0D=Failed to read some data from input stream +archive.0E=Failed to read any data from input stream +archive.0F=L must be between 1..255 +archive.10=Population encoding does not work unless the number of elements are known +archive.11=Cannot calculate token codec from {0} and {1} +archive.12=Cannot have a RunCodec for a negative number of numbers +archive.13=Must supply both codecs for a RunCodec +archive.14=Some unused flags are non-zero +archive.15=1<=b<=5 +archive.16=1<=h<=256 +archive.17=0<=s<=2 +archive.18=0<=d<=1 +archive.19=b=1 -> h=256 +archive.1A=h=256 -> b\!=5 +archive.1B=Delta encoding used without passing in last value; this is a coding error +archive.1C=End of stream reached whilst decoding +archive.1D=Unknown s value +archive.1E=Stream is closed +archive.1F=Unknown format +archive.20=Crc mismatch +archive.21=Size mismatch +archive.22=Cannot read version +archive.23=Entry is not named +archive.24=Unable to open\: {0} +archive.25=Invalid zip file\: {0} +archive.26=attempt to write after finish +archive.27=Needs dictionary +archive.28=No entries +archive.29=Entry already exists: {0} +archive.2A=Name too long: {0} +archive.2B=String is too long +archive.2C=No active entry +archive.2D=Missing version string\: {0} +archive.2E=Line too long +archive.2F=Invalid attribute {0} +archive.30={0} failed verification of {1} +archive.31={0} has invalid digest for {1} in {2} diff --git a/archive/src/main/java/org/apache/harmony/archive/util/Util.java b/archive/src/main/java/org/apache/harmony/archive/util/Util.java new file mode 100644 index 0000000..bed3e91 --- /dev/null +++ b/archive/src/main/java/org/apache/harmony/archive/util/Util.java @@ -0,0 +1,68 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.util; + +public class Util { + + public static boolean ASCIIIgnoreCaseRegionMatches(String s1, int start1, + String s2, int start2, int length) { + + if (s1 != null && s2 != null) { + if (start1 < 0 || length > s1.length() - start1) { + return false; + } + if (start2 < 0 || length > s2.length() - start2) { + return false; + } + + s1 = s1.substring(start1, start1 + length); + s2 = s2.substring(start2, start2 + length); + + return toASCIILowerCase(s1).equals(toASCIILowerCase(s2)); + } + throw new NullPointerException(); + } + + public static String toASCIILowerCase(String s) { + int len = s.length(); + StringBuilder buffer = new StringBuilder(len); + for (int i = 0; i < len; i++) { + char c = s.charAt(i); + if ('A' <= c && c <= 'Z') { + buffer.append((char) (c + ('a' - 'A'))); + } else { + buffer.append(c); + } + } + return buffer.toString(); + } + + public static String toASCIIUpperCase(String s) { + int len = s.length(); + StringBuilder buffer = new StringBuilder(len); + for (int i = 0; i < len; i++) { + char c = s.charAt(i); + if ('a' <= c && c <= 'z') { + buffer.append((char) (c - ('a' - 'A'))); + } else { + buffer.append(c); + } + } + return buffer.toString(); + } +} diff --git a/archive/src/main/native/hy2sie.h b/archive/src/main/native/hy2sie.h new file mode 100644 index 0000000..d40d0e8 --- /dev/null +++ b/archive/src/main/native/hy2sie.h @@ -0,0 +1,114 @@ +#if !defined(hy2sie_h) +#define hy2sie_h + + +#include "JNIHelp.h" +#include "jni.h" +#include "sieb.h" + + +typedef int BOOLEAN; +#define TRUE 1 +#define FALSE 0 + + +// mc: Stuff adopted from hyport.h: + +/** HyMaxPath was chosen from unix MAXPATHLEN. Override in platform + * specific hyfile implementations if needed. + */ +#define HyMaxPath 1024 + + + +// Following definitions from hycomp.h: + +/** + * Define common types: + * <ul> + * <li><code>U_32 / I_32</code> - unsigned/signed 32 bits</li> + * <li><code>U_16 / I_16</code> - unsigned/signed 16 bits</li> + * <li><code>U_8 / I_8</code> - unsigned/signed 8 bits (bytes -- not to be + * confused with char)</li> + * </ul> + */ + +typedef int I_32; +typedef short I_16; +typedef signed char I_8; /* chars can be unsigned */ +typedef unsigned int U_32; +typedef unsigned short U_16; +typedef unsigned char U_8; + +typedef long long I_64; +typedef unsigned long long U_64; + +/** + * Define platform specific types: + * <ul> + * <li><code>UDATA</code> - unsigned data, can be used as an integer or + * pointer storage</li> + * <li><code>IDATA</code> - signed data, can be used as an integer or + * pointer storage</li> + * </ul> + */ +/* FIXME: POINTER64 */ + +typedef I_32 IDATA; +typedef U_32 UDATA; + + +// Further required definitions from Harmony: + +#define HYCONST64(x) x##L + + +#define HY_CFUNC +#define HY_CDATA +#define PROTOTYPE(x) x +#define VMCALL +#define PVMCALL * +#define NORETURN + +#define GLOBAL_DATA(symbol) ((void*)&(symbol)) + + +// Following definitions substitute the HyPortLibrary simply with the JNIEnv + +typedef JNIEnv HyPortLibrary; + +#define PORT_ACCESS_FROM_ENV(env) HyPortLibrary *privatePortLibrary = env +#define PORT_ACCESS_FROM_PORT(portLibrary) HyPortLibrary *privatePortLibrary = portLibrary +#define PORTLIB privatePortLibrary + + +// Following defintion is used to avoide quite a few signedness warnings: +#define mcSignednessBull void * + + +// Following the substitution of hyfile: + +#include <fcntl.h> + +#define HyOpenRead O_RDONLY +#define hyfile_open(a, b, c) open(a, b, c) + +#define HySeekEnd SEEK_END +#define HySeekSet SEEK_SET +#define HySeekCur SEEK_CUR +#define hyfile_seek(a, b, c) lseek(a, b, c) + +#define hyfile_read(a, b, c) read(a, b, c) + +#define hyfile_close(a) close(a) + + +// And further substitutions: + +#define hymem_allocate_memory(byteCount) sieb_malloc(privatePortLibrary, byteCount) +#define hymem_free_memory(pointer) sieb_free(privatePortLibrary, pointer) + +#define ioh_convertToPlatform(path) sieb_convertToPlatform (path) + + +#endif /* hy2sie_h */ diff --git a/archive/src/main/native/hycomp.h b/archive/src/main/native/hycomp.h new file mode 100644 index 0000000..029d4fe --- /dev/null +++ b/archive/src/main/native/hycomp.h @@ -0,0 +1 @@ +#include "hy2sie.h" diff --git a/archive/src/main/native/hymutex.h b/archive/src/main/native/hymutex.h new file mode 100644 index 0000000..5c98ebd --- /dev/null +++ b/archive/src/main/native/hymutex.h @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if !defined(hymutex_h) +#define hymutex_h + +#include <pthread.h> +#include <stdlib.h> + +typedef pthread_mutex_t MUTEX; + +/* MUTEX_INIT */ +#define MUTEX_INIT(mutex) (pthread_mutex_init(&(mutex), NULL) == 0) + +/* MUTEX_DESTROY */ +#define MUTEX_DESTROY(mutex) pthread_mutex_destroy(&(mutex)) + +/* MUTEX_ENTER */ +#define MUTEX_ENTER(mutex) pthread_mutex_lock(&(mutex)) + +/* + * MUTEX_TRY_ENTER + * returns 0 on success + */ +#define MUTEX_TRY_ENTER(mutex) pthread_mutex_trylock(&(mutex)) + +/* MUTEX_EXIT */ +#define MUTEX_EXIT(mutex) pthread_mutex_unlock(&(mutex)) + +#endif /* hymutex_h */ diff --git a/archive/src/main/native/hyport.h b/archive/src/main/native/hyport.h new file mode 100644 index 0000000..029d4fe --- /dev/null +++ b/archive/src/main/native/hyport.h @@ -0,0 +1 @@ +#include "hy2sie.h" diff --git a/archive/src/main/native/java_util_zip_Adler32.c b/archive/src/main/native/java_util_zip_Adler32.c new file mode 100644 index 0000000..a7a182a --- /dev/null +++ b/archive/src/main/native/java_util_zip_Adler32.c @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hy2sie.h" + +#include "zlib.h" + + +JNIEXPORT jlong JNICALL +Java_java_util_zip_Adler32_updateImpl (JNIEnv * env, jobject recv, + jbyteArray buf, int off, int len, + jlong crc) +{ + PORT_ACCESS_FROM_ENV (env); + + jbyte *b; + jboolean isCopy; + jlong result; + + b = (*env)->GetPrimitiveArrayCritical (env, buf, &isCopy); + result = (jlong) adler32 ((uLong) crc, (Bytef *) (b + off), (uInt) len); + (*env)->ReleasePrimitiveArrayCritical (env, buf, b, JNI_ABORT); + + return result; +} + +JNIEXPORT jlong JNICALL +Java_java_util_zip_Adler32_updateByteImpl (JNIEnv * env, jobject recv, + jint val, jlong crc) +{ + PORT_ACCESS_FROM_ENV (env); + + return adler32 ((uLong) crc, (Bytef *) (&val), 1); +} + + +/* + * JNI registration + */ +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "updateImpl", "([BIIJ)J", Java_java_util_zip_Adler32_updateImpl }, + { "updateByteImpl", "(IJ)J", Java_java_util_zip_Adler32_updateByteImpl }, +}; +int register_java_util_zip_Adler32(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "java/util/zip/Adler32", + gMethods, NELEM(gMethods)); +} diff --git a/archive/src/main/native/java_util_zip_CRC32.c b/archive/src/main/native/java_util_zip_CRC32.c new file mode 100644 index 0000000..0688868 --- /dev/null +++ b/archive/src/main/native/java_util_zip_CRC32.c @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hy2sie.h" + +#include "zlib.h" + +JNIEXPORT jlong JNICALL +Java_java_util_zip_CRC32_updateImpl (JNIEnv * env, jobject recv, + jbyteArray buf, int off, int len, + jlong crc) +{ + jbyte *b; + jlong result; + + b = ((*env)->GetPrimitiveArrayCritical (env, buf, 0)); + if (b == NULL) + return -1; + result = crc32 ((uLong) crc, (Bytef *) (b + off), (uInt) len); + ((*env)->ReleasePrimitiveArrayCritical (env, buf, b, JNI_ABORT)); + return result; +} + +JNIEXPORT jlong JNICALL +Java_java_util_zip_CRC32_updateByteImpl (JNIEnv * env, jobject recv, + jbyte val, jlong crc) +{ + return crc32 ((uLong) crc, (Bytef *) (&val), 1); +} + + +/* + * JNI registration + */ +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "updateImpl", "([BIIJ)J", Java_java_util_zip_CRC32_updateImpl }, + { "updateByteImpl", "(BJ)J", Java_java_util_zip_CRC32_updateByteImpl }, +}; +int register_java_util_zip_CRC32(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "java/util/zip/CRC32", + gMethods, NELEM(gMethods)); +} diff --git a/archive/src/main/native/java_util_zip_Deflater.c b/archive/src/main/native/java_util_zip_Deflater.c new file mode 100644 index 0000000..c8bd199 --- /dev/null +++ b/archive/src/main/native/java_util_zip_Deflater.c @@ -0,0 +1,312 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hy2sie.h" + +#include "zlib.h" +#include "zipsup.h" + + +void zfree PROTOTYPE ((void *opaque, void *address)); +void *zalloc PROTOTYPE ((void *opaque, U_32 items, U_32 size)); + + +static struct { + jfieldID inRead; + jfieldID finished; +} gCachedFields; + + +JNIEXPORT void JNICALL +Java_java_util_zip_Deflater_setDictionaryImpl (JNIEnv * env, jobject recv, + jbyteArray dict, int off, + int len, jlong handle) +{ + PORT_ACCESS_FROM_ENV (env); + int err = 0; + unsigned char *dBytes; + JCLZipStream *stream = (JCLZipStream *) ((IDATA) handle); + + dBytes = jclmem_allocate_memory (env, len); + if (dBytes == NULL) + { + throwNewOutOfMemoryError (env, ""); + return; + } + (*env)->GetByteArrayRegion (env, dict, off, len, (mcSignednessBull)dBytes); + err = deflateSetDictionary (stream->stream, (Bytef *) dBytes, len); + if (err != Z_OK) + { + jclmem_free_memory (env, dBytes); + throwNewIllegalArgumentException (env, ""); + return; + } + stream->dict = dBytes; +} + +JNIEXPORT jlong JNICALL +Java_java_util_zip_Deflater_getTotalInImpl (JNIEnv * env, jobject recv, + jlong handle) +{ + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + return stream->stream->total_in; +} + +JNIEXPORT jlong JNICALL +Java_java_util_zip_Deflater_getTotalOutImpl (JNIEnv * env, jobject recv, + jlong handle) +{ + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + return stream->stream->total_out; +} + +JNIEXPORT jint JNICALL +Java_java_util_zip_Deflater_getAdlerImpl (JNIEnv * env, jobject recv, + jlong handle) +{ + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + + return stream->stream->adler; +} + +/* Create a new stream . This stream cannot be used until it has been properly initialized. */ +JNIEXPORT jlong JNICALL +Java_java_util_zip_Deflater_createStream (JNIEnv * env, jobject recv, + jint level, jint strategy, + jboolean noHeader) +{ + PORT_ACCESS_FROM_ENV (env); + + JCLZipStream *jstream; + z_stream *stream; + int err = 0; + // BEGIN android-changed + int wbits = 12; // Was 15, made it 12 to reduce memory consumption. Use MAX + // for fastest. + int mlevel = 5; // Was 9, made it 5 to reduce memory consumption. Might result + // in out-of-memory problems according to some web pages. The + // ZLIB docs are a bit vague, unfortunately. The default + // results in 2 x 128K being allocated per Deflater, which is + // not acceptable. + // END android-changed + + /*Allocate mem for wrapped struct */ + jstream = jclmem_allocate_memory (env, sizeof (JCLZipStream)); + if (jstream == NULL) + { + throwNewOutOfMemoryError (env, ""); + return -1; + } + /*Allocate the z_stream */ + stream = jclmem_allocate_memory (env, sizeof (z_stream)); + if (stream == NULL) + { + jclmem_free_memory (env, jstream); + throwNewOutOfMemoryError (env, ""); + return -1; + } + stream->opaque = (void *) privatePortLibrary; + stream->zalloc = zalloc; + stream->zfree = zfree; + jstream->stream = stream; + jstream->dict = NULL; + jstream->inaddr = NULL; + + /*Unable to find official doc that this is the way to avoid zlib header use. However doc in zipsup.c claims it is so */ + if (noHeader) + wbits = wbits / -1; + err = deflateInit2 (stream, level, Z_DEFLATED, /*Only supported ZLIB method */ + wbits, /*Window bits to use. 15 is fastest but consumes the most memory */ + // BEGIN android-changed + mlevel, /*Memory allocation for internal compression state. 9 uses the most. */ + // END android-changed + strategy); + if (err != Z_OK) + { + throwNewIllegalArgumentException (env, ""); + return -1; + } + + return (jlong) ((IDATA) jstream); +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Deflater_setInputImpl (JNIEnv * env, jobject recv, + jbyteArray buf, jint off, jint len, + jlong handle) +{ + PORT_ACCESS_FROM_ENV (env); + + jbyte *in; + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + if (stream->inaddr != NULL) /*Input has already been provided, free the old buffer */ + jclmem_free_memory (env, stream->inaddr); + stream->inaddr = jclmem_allocate_memory (env, len); + if (stream->inaddr == NULL) + { + throwNewOutOfMemoryError (env, ""); + return; + } + in = ((*env)->GetPrimitiveArrayCritical (env, buf, 0)); + if (in == NULL) + return; + memcpy (stream->inaddr, (in + off), len); + ((*env)->ReleasePrimitiveArrayCritical (env, buf, in, JNI_ABORT)); + stream->stream->next_in = (Bytef *) stream->inaddr; + stream->stream->avail_in = len; + + return; +} + +JNIEXPORT jint JNICALL +Java_java_util_zip_Deflater_deflateImpl (JNIEnv * env, jobject recv, + jbyteArray buf, int off, int len, + jlong handle, int flushParm) +{ + PORT_ACCESS_FROM_ENV (env); + + jbyte *out; + JCLZipStream *stream; + jint err = 0; + jint sin, sout, inBytes = 0; + + /* We need to get the number of bytes already read */ + inBytes = + ((*env)-> + GetIntField (env, recv, + gCachedFields.inRead)); + + stream = (JCLZipStream *) ((IDATA) handle); + stream->stream->avail_out = len; + sin = stream->stream->total_in; + sout = stream->stream->total_out; + out = ((*env)->GetPrimitiveArrayCritical (env, buf, 0)); + if (out == NULL) + return -1; + stream->stream->next_out = (Bytef *) out + off; + err = deflate (stream->stream, flushParm); + ((*env)->ReleasePrimitiveArrayCritical (env, buf, out, 0)); + if (err != Z_OK) + { + if (err == Z_STREAM_END) + { + ((*env)-> + SetBooleanField (env, recv, + gCachedFields.finished, + JNI_TRUE)); + return stream->stream->total_out - sout; + } + } + if (flushParm != Z_FINISH) + { + /* Need to update the number of input bytes read. */ + ((*env)-> + SetIntField (env, recv, + gCachedFields.inRead, + (jint) stream->stream->total_in - sin + inBytes)); + } + return stream->stream->total_out - sout; +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Deflater_endImpl (JNIEnv * env, jobject recv, jlong handle) +{ + PORT_ACCESS_FROM_ENV (env); + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + + deflateEnd (stream->stream); + if (stream->inaddr != NULL) + jclmem_free_memory (env, stream->inaddr); + if (stream->dict != NULL) + jclmem_free_memory (env, stream->dict); + jclmem_free_memory (env, stream->stream); + jclmem_free_memory (env, stream); +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Deflater_resetImpl (JNIEnv * env, jobject recv, + jlong handle) +{ + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + deflateReset (stream->stream); +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Deflater_setLevelsImpl (JNIEnv * env, jobject recv, + int level, int strategy, + jlong handle) +{ + PORT_ACCESS_FROM_ENV (env); + + JCLZipStream *stream; + jbyte b = 0; + int err = 0; + + if (handle == -1) + { + throwNewIllegalStateException (env, ""); + return; + } + stream = (JCLZipStream *) ((IDATA) handle); + stream->stream->next_out = (Bytef *) & b; + err = deflateParams (stream->stream, level, strategy); + if (err != Z_OK) + throwNewIllegalStateException (env, ""); +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Deflater_oneTimeInitialization (JNIEnv * env, jclass clazz) +{ + memset(&gCachedFields, 0, sizeof(gCachedFields)); + gCachedFields.inRead = (*env)->GetFieldID (env, clazz, "inRead", "I"); + gCachedFields.finished = (*env)->GetFieldID (env, clazz, "finished", "Z"); +} + +/* + * JNI registration + */ +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "setDictionaryImpl", "([BIIJ)V", Java_java_util_zip_Deflater_setDictionaryImpl }, + { "getTotalInImpl", "(J)J", Java_java_util_zip_Deflater_getTotalInImpl }, + { "getTotalOutImpl", "(J)J", Java_java_util_zip_Deflater_getTotalOutImpl }, + { "getAdlerImpl", "(J)I", Java_java_util_zip_Deflater_getAdlerImpl }, + { "createStream", "(IIZ)J", Java_java_util_zip_Deflater_createStream }, + { "setInputImpl", "([BIIJ)V", Java_java_util_zip_Deflater_setInputImpl }, + { "deflateImpl", "([BIIJI)I", Java_java_util_zip_Deflater_deflateImpl }, + { "endImpl", "(J)V", Java_java_util_zip_Deflater_endImpl }, + { "resetImpl", "(J)V", Java_java_util_zip_Deflater_resetImpl }, + { "setLevelsImpl", "(IIJ)V", Java_java_util_zip_Deflater_setLevelsImpl }, + { "oneTimeInitialization", "()V", Java_java_util_zip_Deflater_oneTimeInitialization }, +}; +int register_java_util_zip_Deflater(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "java/util/zip/Deflater", + gMethods, NELEM(gMethods)); +} diff --git a/archive/src/main/native/java_util_zip_Inflater.c b/archive/src/main/native/java_util_zip_Inflater.c new file mode 100644 index 0000000..d3a7d7c --- /dev/null +++ b/archive/src/main/native/java_util_zip_Inflater.c @@ -0,0 +1,356 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hy2sie.h" + +#include "zlib.h" +#include <memory.h> +#define jclmem_allocate_memory(env, byteCount) sieb_malloc(env, byteCount) +#define jclmem_free_memory(env, pointer) sieb_free(env, pointer) + +#include <fcntl.h> + + +void zfree PROTOTYPE ((void *opaque, void *address)); +void *zalloc PROTOTYPE ((void *opaque, U_32 items, U_32 size)); + + +static struct { + jfieldID inRead; + jfieldID finished; + jfieldID needsDictionary; +} gCachedFields; + + +// Contents from Harmony's inflater.h was put here: +// +typedef struct JCLZipStream +{ + U_8 *inaddr; + int inCap; + U_8 *dict; + z_stream *stream; +} JCLZipStream; + + + +/** + * Throw java.util.zip.DataFormatException + */ +void +throwNewDataFormatException (JNIEnv * env, const char *message) +{ + jniThrowException(env, "java/util/zip/DataFormatException", message); +} + + +/* Create a new stream . This stream cannot be used until it has been properly initialized. */ +JNIEXPORT jlong JNICALL +Java_java_util_zip_Inflater_createStream (JNIEnv * env, jobject recv, + jboolean noHeader) +{ + PORT_ACCESS_FROM_ENV (env); + + JCLZipStream *jstream; + z_stream *stream; + int err = 0; + int wbits = 15; /*Use MAX for fastest */ + + /*Allocate mem for wrapped struct */ + jstream = jclmem_allocate_memory (env, sizeof (JCLZipStream)); + if (jstream == NULL) + { + throwNewOutOfMemoryError (env, ""); + return -1; + } + + /*Allocate the z_stream */ + stream = jclmem_allocate_memory (env, sizeof (z_stream)); + if (stream == NULL) + { + jclmem_free_memory (env, jstream); + throwNewOutOfMemoryError (env, ""); + return -1; + } + stream->opaque = (void *) privatePortLibrary; + stream->zalloc = zalloc; + stream->zfree = zfree; + stream->adler = 1; + jstream->stream = stream; + jstream->dict = NULL; + jstream->inaddr = NULL; + jstream->inCap = 0; + + /*Unable to find official doc that this is the way to avoid zlib header use. However doc in zipsup.c claims it is so. */ + if (noHeader) + wbits = wbits / -1; + err = inflateInit2 (stream, wbits); /*Window bits to use. 15 is fastest but consumes the most memory */ + + if (err != Z_OK) + { + jclmem_free_memory (env, stream); + jclmem_free_memory (env, jstream); + throwNewIllegalArgumentException (env, ""); + return -1; + } + + return (jlong) ((IDATA) jstream); +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Inflater_setInputImpl (JNIEnv * env, jobject recv, + jbyteArray buf, jint off, jint len, + jlong handle) +{ + PORT_ACCESS_FROM_ENV (env); + + jbyte *in; + U_8 *baseAddr; + JCLZipStream *stream = (JCLZipStream *) ((IDATA) handle); + + if (stream->inaddr != NULL) /*Input has already been provided, free the old buffer */ + jclmem_free_memory (env, stream->inaddr); + baseAddr = jclmem_allocate_memory (env, len); + if (baseAddr == NULL) + { + throwNewOutOfMemoryError (env, ""); + return; + } + stream->inaddr = baseAddr; + stream->stream->next_in = (Bytef *) baseAddr; + stream->stream->avail_in = len; + in = ((*env)->GetPrimitiveArrayCritical (env, buf, 0)); + if (in == NULL) + return; + memcpy (baseAddr, (in + off), len); + ((*env)->ReleasePrimitiveArrayCritical (env, buf, in, JNI_ABORT)); + return; +} + +JNIEXPORT jint JNICALL +Java_java_util_zip_Inflater_setFileInputImpl (JNIEnv * env, jobject recv, + jobject javaFileDescriptor, jlong off, jint len, jlong handle) +{ + PORT_ACCESS_FROM_ENV (env); + + U_8 * baseAddr; + JCLZipStream * stream = (JCLZipStream *) ((IDATA) handle); + + if (stream->inCap < len) { + // No input buffer as yet (or one that is too small). + jclmem_free_memory(env, stream->inaddr); + baseAddr = jclmem_allocate_memory(env, len); + if (baseAddr == NULL) + { + throwNewOutOfMemoryError(env, ""); + return -1; + } + stream->inaddr = baseAddr; + } + stream->stream->next_in = (Bytef *) stream->inaddr; + stream->stream->avail_in = len; + + int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); + lseek(fd, off, SEEK_SET); + int cnt = read(fd, stream->inaddr, len); + + return cnt; +} + +JNIEXPORT jint JNICALL +Java_java_util_zip_Inflater_inflateImpl (JNIEnv * env, jobject recv, + jbyteArray buf, int off, int len, + jlong handle) +{ + PORT_ACCESS_FROM_ENV (env); + + jbyte *out; + JCLZipStream *stream = (JCLZipStream *) ((IDATA) handle); + jint err = 0; + jfieldID fid = 0, fid2 = 0; + jint sin, sout, inBytes = 0; + + /* We need to get the number of bytes already read */ + fid = gCachedFields.inRead; + inBytes = ((*env)->GetIntField (env, recv, fid)); + + stream->stream->avail_out = len; + sin = stream->stream->total_in; + sout = stream->stream->total_out; + out = ((*env)->GetPrimitiveArrayCritical (env, buf, 0)); + + if (out == NULL) + return -1; + stream->stream->next_out = (Bytef *) out + off; + err = inflate (stream->stream, Z_SYNC_FLUSH); + ((*env)->ReleasePrimitiveArrayCritical (env, buf, out, 0)); + + if (err != Z_OK) + { + if(err == Z_STREAM_ERROR) { + return 0; + } + if (err == Z_STREAM_END || err == Z_NEED_DICT) + { + ((*env)->SetIntField (env, recv, fid, (jint) stream->stream->total_in - sin + inBytes)); /* Update inRead */ + if (err == Z_STREAM_END) + fid2 = gCachedFields.finished; + else + fid2 = gCachedFields.needsDictionary; + + ((*env)->SetBooleanField (env, recv, fid2, JNI_TRUE)); + return stream->stream->total_out - sout; + } + else + { + throwNewDataFormatException (env, ""); + return -1; + } + } + + /* Need to update the number of input bytes read. Is there a better way + * (Maybe global the fid then delete when end is called)? + */ + ((*env)-> + SetIntField (env, recv, fid, + (jint) stream->stream->total_in - sin + inBytes)); + + return stream->stream->total_out - sout; +} + +JNIEXPORT jint JNICALL +Java_java_util_zip_Inflater_getAdlerImpl (JNIEnv * env, jobject recv, + jlong handle) +{ + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + + return stream->stream->adler; +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Inflater_endImpl (JNIEnv * env, jobject recv, jlong handle) +{ + PORT_ACCESS_FROM_ENV (env); + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + inflateEnd (stream->stream); + if (stream->inaddr != NULL) /*Input has been provided, free the buffer */ + jclmem_free_memory (env, stream->inaddr); + if (stream->dict != NULL) + jclmem_free_memory (env, stream->dict); + jclmem_free_memory (env, stream->stream); + jclmem_free_memory (env, stream); +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Inflater_setDictionaryImpl (JNIEnv * env, jobject recv, + jbyteArray dict, int off, + int len, jlong handle) +{ + PORT_ACCESS_FROM_ENV (env); + int err = 0; + U_8 *dBytes; + JCLZipStream *stream = (JCLZipStream *) ((IDATA) handle); + + dBytes = jclmem_allocate_memory (env, len); + if (dBytes == NULL) + { + throwNewOutOfMemoryError (env, ""); + return; + } + (*env)->GetByteArrayRegion (env, dict, off, len, (mcSignednessBull)dBytes); + err = inflateSetDictionary (stream->stream, (Bytef *) dBytes, len); + if (err != Z_OK) + { + jclmem_free_memory (env, dBytes); + throwNewIllegalArgumentException (env, ""); + return; + } + stream->dict = dBytes; +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Inflater_resetImpl (JNIEnv * env, jobject recv, + jlong handle) +{ + JCLZipStream *stream; + int err = 0; + stream = (JCLZipStream *) ((IDATA) handle); + + err = inflateReset (stream->stream); + if (err != Z_OK) + { + throwNewIllegalArgumentException (env, ""); + return; + } +} + + +JNIEXPORT jlong JNICALL +Java_java_util_zip_Inflater_getTotalOutImpl (JNIEnv * env, jobject recv, + jlong handle) +{ + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + return stream->stream->total_out; +} + +JNIEXPORT jlong JNICALL +Java_java_util_zip_Inflater_getTotalInImpl (JNIEnv * env, jobject recv, + jlong handle) +{ + JCLZipStream *stream; + + stream = (JCLZipStream *) ((IDATA) handle); + return stream->stream->total_in; +} + +JNIEXPORT void JNICALL +Java_java_util_zip_Inflater_oneTimeInitialization (JNIEnv * env, jclass clazz) +{ + memset(&gCachedFields, 0, sizeof(gCachedFields)); + gCachedFields.inRead = (*env)->GetFieldID (env, clazz, "inRead", "I"); + gCachedFields.finished = (*env)->GetFieldID (env, clazz, "finished", "Z"); + gCachedFields.needsDictionary = (*env)->GetFieldID (env, clazz, "needsDictionary", "Z"); +} + +/* + * JNI registration + */ +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "createStream", "(Z)J", Java_java_util_zip_Inflater_createStream }, + { "setInputImpl", "([BIIJ)V", Java_java_util_zip_Inflater_setInputImpl }, + { "setFileInputImpl", "(Ljava/io/FileDescriptor;JIJ)I", Java_java_util_zip_Inflater_setFileInputImpl }, + { "inflateImpl", "([BIIJ)I", Java_java_util_zip_Inflater_inflateImpl }, + { "getAdlerImpl", "(J)I", Java_java_util_zip_Inflater_getAdlerImpl }, + { "endImpl", "(J)V", Java_java_util_zip_Inflater_endImpl }, + { "setDictionaryImpl", "([BIIJ)V", Java_java_util_zip_Inflater_setDictionaryImpl }, + { "resetImpl", "(J)V", Java_java_util_zip_Inflater_resetImpl }, + { "getTotalOutImpl", "(J)J", Java_java_util_zip_Inflater_getTotalOutImpl }, + { "getTotalInImpl", "(J)J", Java_java_util_zip_Inflater_getTotalInImpl }, + { "oneTimeInitialization", "()V", Java_java_util_zip_Inflater_oneTimeInitialization }, +}; +int register_java_util_zip_Inflater(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "java/util/zip/Inflater", + gMethods, NELEM(gMethods)); +} diff --git a/archive/src/main/native/sieb.c b/archive/src/main/native/sieb.c new file mode 100644 index 0000000..ab9430b --- /dev/null +++ b/archive/src/main/native/sieb.c @@ -0,0 +1,54 @@ +#include "sieb.h" +#include "JNIHelp.h" +#include "jni.h" + +#include <malloc.h> + +// Throw java.lang.OutOfMemoryError +void throwNewOutOfMemoryError (JNIEnv * env, const char *message) +{ + jniThrowException(env, "java/lang/OutOfMemoryError", message); +} + +// Throw java.lang.IllegalStateException +void throwNewIllegalStateException (JNIEnv * env, const char *message) +{ + jniThrowException(env, "java/lang/IllegalStateException", message); +} + +// Throw java.lang.IllegalArgumentException +void throwNewIllegalArgumentException (JNIEnv * env, const char *message) +{ + jniThrowException(env, "java/lang/IllegalArgumentException", message); +} + + + +void * sieb_malloc (JNIEnv * env, size_t byteCnt) { + void * adr = malloc(byteCnt); + if (adr == 0) { + if (byteCnt == 0) + throwNewOutOfMemoryError(env, "sieb_malloc(0) NOT ALLOWED"); + else + throwNewOutOfMemoryError(env, "sieb_malloc"); + } + return adr; +} + +void sieb_free (JNIEnv * env, void * adr) { + free(adr); +} + + + +void sieb_convertToPlatform (char *path) { + char *pathIndex; + + pathIndex = path; + while (*pathIndex != '\0') { + if(*pathIndex == '\\') { + *pathIndex = '/'; + } + pathIndex++; + } +} diff --git a/archive/src/main/native/sieb.h b/archive/src/main/native/sieb.h new file mode 100644 index 0000000..536c806 --- /dev/null +++ b/archive/src/main/native/sieb.h @@ -0,0 +1,22 @@ +#if !defined(sieb_h) +#define sieb_h + + +#include "JNIHelp.h" +#include "jni.h" + + + +void throwNewOutOfMemoryError (JNIEnv * env, const char *message); +void throwNewIllegalArgumentException (JNIEnv * env, const char *message); +void throwNewIllegalStateException (JNIEnv * env, const char *message); + + +void * sieb_malloc (JNIEnv * env, size_t byteCnt); +void sieb_free (JNIEnv * env, void * adr); + +void sieb_convertToPlatform (char *path); + + + +#endif /* sieb_h */ diff --git a/archive/src/main/native/sub.mk b/archive/src/main/native/sub.mk new file mode 100644 index 0000000..047c319 --- /dev/null +++ b/archive/src/main/native/sub.mk @@ -0,0 +1,23 @@ +# This file is included by the top-level libcore Android.mk. +# It's not a normal makefile, so we don't include CLEAR_VARS +# or BUILD_*_LIBRARY. + +LOCAL_SRC_FILES := \ + java_util_zip_Adler32.c \ + java_util_zip_CRC32.c \ + java_util_zip_Deflater.c \ + java_util_zip_Inflater.c \ + zipalloc.c \ + sieb.c + +LOCAL_C_INCLUDES += \ + external/zlib + +# Any shared/static libs that are listed here must also +# be listed in libs/nativehelper/Android.mk. +# TODO: fix this requirement + +LOCAL_SHARED_LIBRARIES += \ + libz + +LOCAL_STATIC_LIBRARIES += diff --git a/archive/src/main/native/zipalloc.c b/archive/src/main/native/zipalloc.c new file mode 100644 index 0000000..2b4966b --- /dev/null +++ b/archive/src/main/native/zipalloc.c @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "hyport.h" + +#include "zlib.h" + +#define CDEV_CURRENT_FUNCTION _prototypes_private + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION _prototypes_public +void *zalloc PROTOTYPE ((void *opaque, U_32 items, U_32 size)); +void zfree PROTOTYPE ((void *opaque, void *address)); + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zalloc + +/* + ZLib interface to hymem_allocate_memory. +*/ +void * +zalloc (void *opaque, U_32 items, U_32 size) +{ + PORT_ACCESS_FROM_PORT (((HyPortLibrary *) opaque)); + + return hymem_allocate_memory (items * size); +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zfree + +/* + ZLib interface to hymem_free_memory. +*/ +void +zfree (void *opaque, void *address) +{ + PORT_ACCESS_FROM_PORT ((HyPortLibrary *) opaque); + + hymem_free_memory (address); +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION + +#undef CDEV_CURRENT_FUNCTION diff --git a/archive/src/main/native/zipsup.c b/archive/src/main/native/zipsup.c new file mode 100644 index 0000000..1bbe51f --- /dev/null +++ b/archive/src/main/native/zipsup.c @@ -0,0 +1,2319 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @file + * @ingroup ZipSupport + * @brief Zip Support for Java VM +*/ + +#include <string.h> +#include <sys/stat.h> + +#include "hy2sie.h" +#include "zipsup.h" + +#include "zlib.h" + +// zlib is statically linked for Android: +#define checkZipLibrary(dummy) 0 +#ifdef checkZipLibrary +#define inflateInit2Func(a, b, c, d) inflateInit2_ (a, b, c, d) +#define inflateFunc(a, b) inflate (a, b) +#define inflateEndFunc(a) inflateEnd (a) +#else +/* Globals for the zip library */ +UDATA zipDLLDescriptor = 0; +int (*inflateInit2Func) (void *, int, const char *, int); +int (*inflateFunc) (void *, int); +int (*inflateEndFunc) (void *); +#endif + +#define ZIP_NEXT_U8(value, index) (value = *(index++)) +#define ZIP_NEXT_U16(value, index) ((value = (index[1] << 8) | index[0]), index += 2, value) +#define ZIP_NEXT_U32(value, index) ((value = ((U_32)index[3] << 24) | ((U_32)index[2] << 16) | ((U_32)index[1] << 8) | (U_32)index[0]), index += 4, value) + +#define WORK_BUFFER_SIZE 64000 + +#define SCAN_CHUNK_SIZE 1024 + +struct workBuffer +{ + HyPortLibrary *portLib; + UDATA *bufferStart; + UDATA *bufferEnd; + UDATA *currentAlloc; + UDATA cntr; +}; + +#define CDEV_CURRENT_FUNCTION _prototypes_private +I_32 zip_populateCache +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile)); + +static I_32 inflateData +PROTOTYPE ((struct workBuffer * workBuf, U_8 * inputBuffer, + U_32 inputBufferSize, U_8 * outputBuffer, U_32 outputBufferSize)); + +I_32 checkZipLibrary PROTOTYPE ((HyPortLibrary * portLib)); + +I_32 scanForDataDescriptor +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * zipEntry)); +void zdatafree PROTOTYPE ((void *opaque, void *address)); +static I_32 readZipEntry +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * zipEntry, const char *filename, + IDATA * enumerationPointer, IDATA * entryStart, + BOOLEAN findDirectory)); +I_32 scanForCentralEnd +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipCentralEnd * endEntry)); +void *zdataalloc PROTOTYPE ((void *opaque, U_32 items, U_32 size)); + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION _prototypes_public +I_32 zip_getZipEntryData +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, HyZipEntry * entry, + U_8 * buffer, U_32 bufferSize)); +I_32 zip_getZipEntryFromOffset +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, HyZipEntry * entry, + IDATA offset)); +I_32 zip_establishCache +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile)); +void zip_resetZipFile +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + IDATA * nextEntryPointer)); +I_32 zip_getNextZipEntry +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * zipEntry, IDATA * nextEntryPointer)); +I_32 zip_getZipEntry +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, HyZipEntry * entry, + const char *filename, BOOLEAN findDirectory)); +I_32 zip_getZipEntryExtraField +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, HyZipEntry * entry, + U_8 * buffer, U_32 bufferSize)); +void zip_initZipEntry +PROTOTYPE ((HyPortLibrary * portLib, HyZipEntry * entry)); +I_32 zip_openZipFile +PROTOTYPE ((HyPortLibrary * portLib, char *filename, HyZipFile * zipFile, + HyZipCachePool * cachePool)); +void zip_freeZipEntry +PROTOTYPE ((HyPortLibrary * portLib, HyZipEntry * entry)); +I_32 VMCALL zip_closeZipFile +PROTOTYPE ((HyPortLibrary * portLib, struct HyZipFile * zipFile)); +I_32 zip_getZipEntryComment +PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, HyZipEntry * entry, + U_8 * buffer, U_32 bufferSize)); + +#undef CDEV_CURRENT_FUNCTION + +//#include "hythread.h" +//#define ENTER() hythread_monitor_enter(hythread_global_monitor()) +//#define EXIT() hythread_monitor_exit(hythread_global_monitor()) + +#include "hymutex.h" +MUTEX zip_globalMutex; +static int initialized = 0; + +#define ENTER() \ + if (!initialized) { MUTEX_INIT(zip_globalMutex); initialized = 1; } \ + MUTEX_ENTER(zip_globalMutex); + +#define EXIT() MUTEX_EXIT(zip_globalMutex); + + +HyZipCachePool * +zipsup_GetZipCachePool(JNIEnv * env) +{ + static HyZipCachePool *pool = 0; + + if (pool == 0) { + pool = zipCachePool_new(env); + } + return pool; +} + + +#ifndef checkZipLibrary +#define CDEV_CURRENT_FUNCTION checkZipLibrary + +/* + Ensure that the zip library is loaded. + Return 0 on success, -1 on failure. +*/ +I_32 +checkZipLibrary (HyPortLibrary * portLib) +{ + PORT_ACCESS_FROM_PORT (portLib); + + /* if the library has already been loaded return success/failure */ + if (zipDLLDescriptor > 1) + return 0; + if (zipDLLDescriptor == 1) + return -1; + + /* open up the zip library by name */ + + if (hysl_open_shared_library (HY_ZIP_DLL_NAME, &zipDLLDescriptor, TRUE)) + goto openFailed; + + /* look up the functions */ + if (hysl_lookup_name + (zipDLLDescriptor, "inflateInit2_", (void *) &inflateInit2Func, + "ILILI")) + goto loadFailed; + if (hysl_lookup_name + (zipDLLDescriptor, "inflate", (void *) &inflateFunc, "IPI")) + goto loadFailed; + if (hysl_lookup_name + (zipDLLDescriptor, "inflateEnd", (void *) &inflateEndFunc, "IP")) + goto loadFailed; + + /* good to go */ + return 0; + +loadFailed: + hysl_close_shared_library (zipDLLDescriptor); + + /* mark the descriptor as a failed load. only report the error once */ + zipDLLDescriptor = 1; + + /* Unable to open %s (Missing export) */ + hynls_printf (PORTLIB, HYNLS_WARNING, HYNLS_ZIP_MISSING_EXPORT, + HY_ZIP_DLL_NAME); + + return -1; + +openFailed: + /* mark the descriptor as a failed load. only report the error once */ + zipDLLDescriptor = 1; + + /* Unable to open %s (%s) */ + hynls_printf (PORTLIB, HYNLS_WARNING, HYNLS_ZIP_UNABLE_TO_OPEN_ZIP_DLL, + HY_ZIP_DLL_NAME, hyerror_last_error_message ()); + return -1; +} + +#undef CDEV_CURRENT_FUNCTION +#endif + +#define CDEV_CURRENT_FUNCTION inflateData + +/* + Returns 0 on success or one of the following: + ZIP_ERR_UNSUPPORTED_FILE_TYPE + ZIP_ERR_FILE_CORRUPT + ZIP_ERR_OUT_OF_MEMORY + ZIP_ERR_INTERNAL_ERROR +*/ +static I_32 +inflateData (struct workBuffer *workBuf, U_8 * inputBuffer, + U_32 inputBufferSize, U_8 * outputBuffer, U_32 outputBufferSize) +{ + PORT_ACCESS_FROM_PORT (workBuf->portLib); + + z_stream stream; + I_32 err; + + stream.next_in = inputBuffer; + stream.avail_in = inputBufferSize; + stream.next_out = outputBuffer; + stream.avail_out = outputBufferSize; + + stream.opaque = workBuf; + stream.zalloc = zdataalloc; + stream.zfree = zdatafree; + + /* Initialize stream. Pass "-15" as max number of window bits, negated + to indicate that no zlib header is present in the data. */ + err = inflateInit2Func (&stream, -15, ZLIB_VERSION, sizeof (z_stream)); + if (err != Z_OK) + return -1; + + /* Inflate the data. */ + err = inflateFunc (&stream, Z_SYNC_FLUSH); + + /* Clean up the stream. */ + inflateEndFunc (&stream); + + /* Check the return code. Did we complete the inflate? */ + if ((err == Z_STREAM_END) || (err == Z_OK)) + { + if (stream.total_out == outputBufferSize) + { + return 0; + } + } + + switch (err) + { + case Z_OK: /* an error if file is incomplete */ + case Z_STREAM_END: /* an error if file is incomplete */ + case Z_ERRNO: /* a random error */ + case Z_STREAM_ERROR: /* stream inconsistent */ + case Z_DATA_ERROR: /* corrupted zip */ + return ZIP_ERR_FILE_CORRUPT; + + case Z_VERSION_ERROR: /* wrong zlib version */ + case Z_NEED_DICT: /* needs a preset dictionary that we can't provide */ + return ZIP_ERR_UNSUPPORTED_FILE_TYPE; + + case Z_MEM_ERROR: /* out of memory */ + return ZIP_ERR_OUT_OF_MEMORY; + + case Z_BUF_ERROR: /* no progress / out of output buffer */ + default: /* jic */ + return ZIP_ERR_INTERNAL_ERROR; + } +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION scanForCentralEnd +/* + Scan backward from end of file for a central end header. Read from zipFile and update the HyZipCentralEnd provided. + + Returns 0 on success or one of the following: + ZIP_ERR_FILE_READ_ERROR + ZIP_ERR_FILE_CORRUPT +*/ +I_32 +scanForCentralEnd (HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipCentralEnd * endEntry) +{ + U_8 *current; + U_8 buffer[SCAN_CHUNK_SIZE]; + I_32 i, size, state; + U_32 dataSize = 0; + I_64 seekResult; + I_32 fileSize; + I_32 bytesAlreadyRead = 0; + + PORT_ACCESS_FROM_PORT (portLib); + + /* Haven't seen anything yet. */ + state = 0; + + seekResult = hyfile_seek (zipFile->fd, 0, HySeekEnd); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + return ZIP_ERR_FILE_READ_ERROR; + } + fileSize = (I_32) seekResult; + zipFile->pointer = fileSize; + + while (TRUE) + { + /* Fill the buffer. */ + if (bytesAlreadyRead == fileSize) + { + zipFile->pointer = -1; + return ZIP_ERR_FILE_CORRUPT; + } + + size = SCAN_CHUNK_SIZE; + if (size > fileSize - bytesAlreadyRead) + size = fileSize - bytesAlreadyRead; + bytesAlreadyRead += size; + seekResult = + hyfile_seek (zipFile->fd, fileSize - bytesAlreadyRead, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer = (I_32) seekResult; + + if (hyfile_read (zipFile->fd, buffer, size) != size) + { + zipFile->pointer = -1; + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer += size; + + /* Scan the buffer (backwards) for CentralEnd signature = PK^E^F. */ + for (i = size; i--; dataSize++) + { + switch (state) + { + case 0: + /* Nothing yet. */ + if (buffer[i] == 6) + state = 1; + break; + + case 1: + /* Seen ^F */ + if (buffer[i] == 5) + state = 2; + else + state = 0; + break; + + case 2: + /* Seen ^E^F */ + if (buffer[i] == 'K') + state = 3; + else + state = 0; + break; + + case 3: + /* Seen K^E^F */ + if (buffer[i] == 'P' && dataSize >= 21) + { + /* Found it. Read the data from the end-of-central-dir record. */ + current = buffer + i + 4; + ZIP_NEXT_U16 (endEntry->diskNumber, current); + ZIP_NEXT_U16 (endEntry->dirStartDisk, current); + ZIP_NEXT_U16 (endEntry->thisDiskEntries, current); + ZIP_NEXT_U16 (endEntry->totalEntries, current); + ZIP_NEXT_U32 (endEntry->dirSize, current); + ZIP_NEXT_U32 (endEntry->dirOffset, current); + ZIP_NEXT_U16 (endEntry->commentLength, current); + + /* Quick test to ensure that the header isn't invalid. + Current dataSize is the number of bytes of data scanned, up to the ^H in the stream. */ + if (dataSize >= (U_32) (21 + endEntry->commentLength)) + return 0; + + /* Header looked invalid. Pretend we didn't see it and keep scanning.. */ + } + state = 0; + break; + } + } + } +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION scanForDataDescriptor +/* + Scan ahead for a data descriptor. Read from zipFile and update the HyZipLocalHeader provided. + + Returns 0 on success or one of the following: + ZIP_ERR_FILE_READ_ERROR + ZIP_ERR_FILE_CORRUPT +*/ +I_32 +scanForDataDescriptor (HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * zipEntry) +{ + U_8 *current; + U_8 buffer[SCAN_CHUNK_SIZE], descriptor[16]; + I_32 i, size, state; + U_32 dataSize, blockPointer; + I_64 seekResult; + + PORT_ACCESS_FROM_PORT (portLib); + + /* Skip ahead and read the data descriptor. The compressed size should be 0. */ + if (zipFile->pointer != + (IDATA) (zipEntry->dataPointer + zipEntry->compressedSize)) + { + seekResult = + hyfile_seek (zipFile->fd, + zipEntry->dataPointer + zipEntry->compressedSize, + HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer = (I_32) seekResult; + } + + /* Haven't seen anything yet. */ + blockPointer = dataSize = zipEntry->compressedSize; + state = 0; + + /* Scan until we find PK^G^H (otherwise it's an error). */ + while (1) + { + /* Fill the buffer. */ + size = hyfile_read (zipFile->fd, buffer, SCAN_CHUNK_SIZE); + if (size == 0) + { + return ZIP_ERR_FILE_CORRUPT; + } + else if (size < 0) + { + zipFile->pointer = -1; + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer += size; + blockPointer += size; + + /* Scan the buffer. */ + for (i = 0; i < size; i++, dataSize++) + { + switch (state) + { + case 0: + /* Nothing yet. */ + if (buffer[i] == 'P') + { + state = 1; + } + break; + + case 1: + /* Seen P */ + if (buffer[i] == 'K') + { + state = 2; + } + else + state = 0; + break; + + case 2: + /* Seen PK */ + if (buffer[i] == 7) + { + state = 3; + } + else + { + state = 0; + } + break; + + case 3: + /* Seen PK^G */ + if (buffer[i] == 8) + { + /* Found it! Read the descriptor */ + if (i + 12 < size) + { + current = &buffer[i + 1]; + } + else + { + seekResult = + hyfile_seek (zipFile->fd, + zipEntry->dataPointer + dataSize + 1, + HySeekSet); + if ((seekResult < 0) + || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer = (I_32) seekResult; + if (hyfile_read (zipFile->fd, descriptor, 12) != 12) + { + zipFile->pointer = -1; + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer += 12; + current = descriptor; + } + + /* Read the data from the descriptor. */ + ZIP_NEXT_U32 (zipEntry->crc32, current); + ZIP_NEXT_U32 (zipEntry->compressedSize, current); + ZIP_NEXT_U32 (zipEntry->uncompressedSize, current); + + /* Quick test to ensure that the header isn't invalid. + Current dataSize is the number of bytes of data scanned, up to the ^H in the stream. */ + if (dataSize - 3 == zipEntry->compressedSize) + { + return 0; + } + + /* Header looked invalid. Reset the pointer and continue scanning. */ + seekResult = + hyfile_seek (zipFile->fd, + zipEntry->dataPointer + blockPointer, + HySeekSet); + if ((seekResult < 0) + || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer = (I_32) seekResult; + } + else + state = 0; + break; + } + } + } +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_populateCache +/* + Fill in the cache of a given zip file. This should only be called once during zip_openZipFile! + + Returns 0 on success or one of the following: + ZIP_ERR_FILE_READ_ERROR + ZIP_ERR_FILE_OPEN_ERROR + ZIP_ERR_UNKNOWN_FILE_TYPE + ZIP_ERR_UNSUPPORTED_FILE_TYPE + ZIP_ERR_OUT_OF_MEMORY + ZIP_ERR_INTERNAL_ERROR +*/ +I_32 +zip_populateCache (HyPortLibrary * portLib, HyZipFile * zipFile) +{ + PORT_ACCESS_FROM_PORT (portLib); + + I_32 result = 0; + IDATA bufferSize = 65536; + IDATA unreadSize = 0; + IDATA bufferedSize = 0; + IDATA bytesToRead = 0; + IDATA filenameCopied; + HyZipEntry entry; + HyZipCentralEnd endEntry; + U_8 *buffer = NULL; + U_8 *filename = NULL; + IDATA filenameSize = 256; /* Should be sufficient for most filenames */ + U_8 *current; + U_32 sig; + U_32 localHeaderOffset; + IDATA startCentralDir; + I_64 seekResult; + + if (!zipFile->cache) + return ZIP_ERR_INTERNAL_ERROR; + + /* Find and read the end-of-central-dir record. */ + result = scanForCentralEnd (portLib, zipFile, &endEntry); + if (result != 0) + return result; + + unreadSize = endEntry.dirSize + 4 /* slop */ ; + zipFile->cache->startCentralDir = startCentralDir = + (IDATA) ((UDATA) endEntry.dirOffset); + + if (zipFile->pointer != startCentralDir) + { + seekResult = hyfile_seek (zipFile->fd, startCentralDir, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + + zipFile->pointer = (I_32) seekResult; + if (zipFile->pointer != startCentralDir) + { + result = ZIP_ERR_FILE_READ_ERROR; + zipFile->pointer = -1; + goto finished; + } + } + + /* No point in allocating more than we'll actually need.. */ + if (bufferSize > unreadSize) + bufferSize = unreadSize; + + filename = hymem_allocate_memory (filenameSize); + if (!filename) + { + result = ZIP_ERR_OUT_OF_MEMORY; + goto finished; + } + + /* Allocate some space to hold central directory goo as we eat through it */ + buffer = hymem_allocate_memory (bufferSize); + if (!buffer && (bufferSize > 4096)) + { + /* Not enough memory, fall back to a smaller buffer! */ + bufferSize = 4096; + buffer = hymem_allocate_memory (bufferSize); + } + if (!buffer) + { + result = ZIP_ERR_OUT_OF_MEMORY; + goto finished; + } + + while (unreadSize) + { + + /* Read as much as needed into buffer. */ + bytesToRead = bufferSize - bufferedSize; + if (bytesToRead > unreadSize) + bytesToRead = unreadSize; + result = hyfile_read (zipFile->fd, buffer + bufferedSize, bytesToRead); + if (result < 0) + { + result = ZIP_ERR_FILE_READ_ERROR; + zipFile->pointer = -1; + goto finished; + } + zipFile->pointer += result; + unreadSize -= result; + bufferedSize += result; + current = buffer; + + /* consume entries until we run out. */ + while (current + 46 < buffer + bufferedSize) + { + IDATA entryPointer; + + entryPointer = + zipFile->pointer + (current - (buffer + bufferedSize)); + + sig = 0; + ZIP_NEXT_U32 (sig, current); + if (sig == ZIP_CentralEnd) + { + /* We're done here. */ + result = 0; + goto finished; + } + if (sig != ZIP_CentralHeader) + { + /* Umm...What the Hell? */ + result = ZIP_ERR_FILE_CORRUPT; + goto finished; + } + + /* Read ZIP_CentralHeader entry */ + ZIP_NEXT_U16 (entry.versionCreated, current); + ZIP_NEXT_U16 (entry.versionNeeded, current); + ZIP_NEXT_U16 (entry.flags, current); + ZIP_NEXT_U16 (entry.compressionMethod, current); + ZIP_NEXT_U16 (entry.lastModTime, current); + ZIP_NEXT_U16 (entry.lastModDate, current); + ZIP_NEXT_U32 (entry.crc32, current); + ZIP_NEXT_U32 (entry.compressedSize, current); + ZIP_NEXT_U32 (entry.uncompressedSize, current); + ZIP_NEXT_U16 (entry.filenameLength, current); + ZIP_NEXT_U16 (entry.extraFieldLength, current); + ZIP_NEXT_U16 (entry.fileCommentLength, current); + current += sizeof (U_16); /* skip disk number field */ + ZIP_NEXT_U16 (entry.internalAttributes, current); + current += sizeof (U_32); /* skip external attributes field */ + ZIP_NEXT_U32 (localHeaderOffset, current); + + /* Increase filename buffer size if necessary. */ + if (filenameSize < entry.filenameLength + 1) + { + hymem_free_memory (filename); + filenameSize = entry.filenameLength + 1; + filename = hymem_allocate_memory (filenameSize); + if (!filename) + { + result = ZIP_ERR_OUT_OF_MEMORY; + goto finished; + } + } + + filenameCopied = 0; + while (filenameCopied < entry.filenameLength) + { + IDATA size; + /* Copy as much of the filename as we can see in the buffer (probably the whole thing). */ + + size = entry.filenameLength - filenameCopied; + if (size > bufferedSize - (current - buffer)) + { + size = bufferedSize - (current - buffer); + } + memcpy (filename + filenameCopied, current, size); + filenameCopied += size; + current += size; + if (filenameCopied >= entry.filenameLength) + break; /* done */ + + /* Otherwise, we ran out of source string. Load another chunk.. */ + bufferedSize = 0; + if (!unreadSize) + { + /* Central header is supposedly done? Bak */ + result = ZIP_ERR_FILE_CORRUPT; + goto finished; + } + bytesToRead = bufferSize - bufferedSize; + if (bytesToRead > unreadSize) + bytesToRead = unreadSize; + result = + hyfile_read (zipFile->fd, buffer + bufferedSize, bytesToRead); + if (result < 0) + { + result = ZIP_ERR_FILE_READ_ERROR; + zipFile->pointer = -1; + goto finished; + } + zipFile->pointer += result; + unreadSize -= result; + bufferedSize += result; + current = buffer; + } + filename[entry.filenameLength] = '\0'; /* null-terminate */ + + if (((entry.compressionMethod == ZIP_CM_Deflated) + && (entry.flags & 0x8)) || (entry.fileCommentLength != 0)) + { + /* Either local header doesn't know the compressedSize, or this entry has a file + comment. In either case, cache the central header instead of the local header + so we can find the information we need later. */ + + result = + zipCache_addElement (zipFile->cache, (char *) filename, + entryPointer); + + } + else + { + result = + zipCache_addElement (zipFile->cache, (char *) filename, + localHeaderOffset); + } + + if (!result) + { + result = ZIP_ERR_OUT_OF_MEMORY; + goto finished; + } + + /* Skip the data and comment. */ + bytesToRead = entry.extraFieldLength + entry.fileCommentLength; + if (bufferedSize - (current - buffer) >= bytesToRead) + { + current += bytesToRead; + } + else + { + /* The rest of the buffer is uninteresting. Skip ahead to where the good stuff is */ + bytesToRead -= (bufferedSize - (current - buffer)); + current = buffer + bufferedSize; + unreadSize -= bytesToRead; + + seekResult = hyfile_seek (zipFile->fd, bytesToRead, HySeekCur); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer = (I_32) seekResult; + } + } + bufferedSize -= (current - buffer); + memmove (buffer, current, bufferedSize); + } + + result = 0; + +finished: + if (filename) + hymem_free_memory (filename); + if (buffer) + hymem_free_memory (buffer); + return result; +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION readZipEntry +/* + Read the next zip entry for the zipFile into the zipEntry provided. If filename is non-NULL, it is expected to match + the filename read for the entry. If (cachePointer != -1) the filename of the entry will be looked up in the cache (assuming + there is one) to help detect use of an invalid cache. If enumerationPointer is non-NULL, sequential access is assumed and + either a local zip entry or a data descriptor will be accepted, but a central zip entry will cause ZIP_ERR_NO_MORE_ENTRIES + to be returned. If enumerationPointer is NULL, random access is assumed and either a local zip entry or a central zip + entry will be accepted. + + Returns 0 on success or one of the following: + ZIP_ERR_FILE_READ_ERROR + ZIP_ERR_FILE_CORRUPT + ZIP_ERR_OUT_OF_MEMORY + ZIP_ERR_NO_MORE_ENTRIES +*/ +static I_32 +readZipEntry (HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * zipEntry, const char *filename, + IDATA * enumerationPointer, IDATA * entryStart, + BOOLEAN findDirectory) +{ + PORT_ACCESS_FROM_PORT (portLib); + + I_32 result; + U_8 buffer[46 + 128]; + U_8 *current; + U_32 sig; + IDATA readLength; + I_64 seekResult; + U_8 *readBuffer; + IDATA currentEntryPointer, localEntryPointer; + IDATA headerSize; + IDATA filenameLength = filename ? strlen (filename) : 0; + +retry: + if (entryStart) + *entryStart = zipFile->pointer; + readBuffer = NULL; + /* Guess how many bytes we'll need to read. If we guess correctly we will do fewer I/O operations */ + headerSize = 30; /* local zip header size */ + if (zipFile->cache && (zipFile->pointer >= zipFile->cache->startCentralDir)) + { + headerSize = 46; /* central zip header size */ + } + readLength = headerSize + (filename ? filenameLength : 128); + if (findDirectory) + { + /* Extra byte for possible trailing '/' */ + readLength++; + } + + /* Allocate some memory if necessary */ + if (readLength <= sizeof (buffer)) + { + current = buffer; + } + else + { + current = readBuffer = hymem_allocate_memory (readLength); + if (!readBuffer) + return ZIP_ERR_OUT_OF_MEMORY; + } + + currentEntryPointer = localEntryPointer = zipFile->pointer; + + result = hyfile_read (zipFile->fd, current, readLength); + if ((result < 22) + || (filename + && !(result == readLength + || (findDirectory && result == (readLength - 1))))) + { + /* We clearly didn't get enough bytes */ + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer += result; + readLength = result; /* If it's not enough, we'll catch that later */ + ZIP_NEXT_U32 (sig, current); + + if (enumerationPointer) + { + if ((sig == ZIP_CentralEnd)) + { + result = ZIP_ERR_NO_MORE_ENTRIES; + goto finished; + } + } + if ((enumerationPointer || (!zipFile->cache)) + && (sig == ZIP_DataDescriptor)) + { + /* We failed to predict a data descriptor here. This should be an error (i.e. only happens in malformed zips?) + but, but we will silently skip over it */ + seekResult = + hyfile_seek (zipFile->fd, currentEntryPointer + 16, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer = (I_32) seekResult; + + if (zipFile->pointer == currentEntryPointer + 16) + { + if (readBuffer) + { + hymem_free_memory (readBuffer); + } + goto retry; + } + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + + if ((sig != ZIP_CentralHeader) && (sig != ZIP_LocalHeader)) + { + /* Unexpected. */ + result = ZIP_ERR_FILE_CORRUPT; + goto finished; + } + headerSize = ((sig == ZIP_CentralHeader) ? 46 : 30); + if (readLength < headerSize) + { + /* We didn't get the whole header (and none of the filename).. */ + /* NOTE: this could happen in normal use if the assumed filename length above is <16. Since it's 128, we don't + handle the impossible case where we would have to read more header. It could also happen if the caller + supplied a filename of length <16 but that only happens when we have a cache (so we'll know the header size) + */ + result = ZIP_ERR_FILE_READ_ERROR; + } + readLength -= headerSize; + + if (sig == ZIP_CentralHeader) + { + current += 2; /* skip versionCreated field */ + } + ZIP_NEXT_U16 (zipEntry->versionNeeded, current); + ZIP_NEXT_U16 (zipEntry->flags, current); + ZIP_NEXT_U16 (zipEntry->compressionMethod, current); + ZIP_NEXT_U16 (zipEntry->lastModTime, current); + ZIP_NEXT_U16 (zipEntry->lastModDate, current); + ZIP_NEXT_U32 (zipEntry->crc32, current); + ZIP_NEXT_U32 (zipEntry->compressedSize, current); + ZIP_NEXT_U32 (zipEntry->uncompressedSize, current); + ZIP_NEXT_U16 (zipEntry->filenameLength, current); + ZIP_NEXT_U16 (zipEntry->extraFieldLength, current); + zipEntry->fileCommentLength = 0; + + if (sig == ZIP_CentralHeader) + { + ZIP_NEXT_U16 (zipEntry->fileCommentLength, current); + current += 8; /* skip disk number start + internal attrs + external attrs */ + ZIP_NEXT_U32 (localEntryPointer, current); + } + + if (filename) + { + if (zipFile->cache) + { + if (! + (readLength == zipEntry->filenameLength + || (findDirectory + && (readLength - 1) == zipEntry->filenameLength))) + { + /* We knew exactly how much we were supposed to read, and this wasn't it */ + result = ZIP_ERR_FILE_CORRUPT; + goto finished; + } + } + } + + /* Allocate space for filename */ + if (zipEntry->filenameLength >= ZIP_INTERNAL_MAX) + { + zipEntry->filename = + hymem_allocate_memory (zipEntry->filenameLength + 1); + if (!zipEntry->filename) + { + result = ZIP_ERR_OUT_OF_MEMORY; + goto finished; + } + } + else + { + zipEntry->filename = zipEntry->internalFilename; + } + if (readLength > zipEntry->filenameLength) + { + readLength = zipEntry->filenameLength; + } + memcpy (zipEntry->filename, current, readLength); + + /* Read the rest of the filename if necessary. Allocate space in HyZipEntry for it! */ + if (readLength < zipEntry->filenameLength) + { + result = + hyfile_read (zipFile->fd, zipEntry->filename + readLength, + zipEntry->filenameLength - readLength); + if (result != (zipEntry->filenameLength - readLength)) + { + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer += result; + } + zipEntry->filename[zipEntry->filenameLength] = '\0'; + + /* If we know what filename is supposed to be, compare it and make sure it matches */ + /* Note: CASE-SENSITIVE COMPARE because filenames in zips are case sensitive (even on platforms with + case-insensitive file systems) */ + if (filename) + { + if (! + ((findDirectory && zipEntry->filenameLength == (filenameLength + 1) + && zipEntry->filename[filenameLength] == '/' + && !strncmp ((char *) zipEntry->filename, (const char *) filename, + filenameLength)) + || !strcmp ((const char *) zipEntry->filename, + (const char *) filename))) + { + /* We seem to have read something totally invalid.. */ + result = ZIP_ERR_FILE_CORRUPT; + goto finished; + } + } + + zipEntry->filenamePointer = currentEntryPointer + headerSize; + zipEntry->extraFieldPointer = + localEntryPointer + 30 + zipEntry->filenameLength; + zipEntry->dataPointer = + zipEntry->extraFieldPointer + zipEntry->extraFieldLength; + zipEntry->extraField = NULL; + zipEntry->fileCommentPointer = 0; + zipEntry->fileComment = NULL; + zipEntry->data = NULL; + + if (sig == ZIP_CentralHeader) + { + U_8 buf[2]; + U_8 *buf2 = buf; + U_16 lost; + /* Also, we know where the comment is */ + zipEntry->fileCommentPointer = currentEntryPointer + headerSize + + zipEntry->filenameLength + zipEntry->extraFieldLength; + if (hyfile_seek (zipFile->fd, localEntryPointer + 28, HySeekSet) == + localEntryPointer + 28) + { + if (hyfile_read (zipFile->fd, buf, 2) == 2) + { + ZIP_NEXT_U16 (lost, buf2); + zipEntry->dataPointer = zipEntry->extraFieldPointer + lost; + zipFile->pointer = localEntryPointer + 30; + } + } + } + + if ((sig == ZIP_LocalHeader) + && (zipEntry->compressionMethod == ZIP_CM_Deflated) + && (zipEntry->flags & 0x8)) + { + /* What we just read doesn't tell us how big the compressed data is. We have to do a heuristic search for a + valid data descriptor at the end of the compressed text */ + result = scanForDataDescriptor (portLib, zipFile, zipEntry); + if (result < 0) + goto finished; + } + + /* Entry read successfully */ + + if (enumerationPointer) + { + /* Work out where the next entry is supposed to be */ + *enumerationPointer = + zipEntry->fileCommentPointer + zipEntry->fileCommentLength; + } + + if (readBuffer) + hymem_free_memory (readBuffer); + return 0; + +finished: + if (readBuffer) + { + hymem_free_memory (readBuffer); + } + if ((zipEntry->filename) + && (zipEntry->filename != zipEntry->internalFilename)) + { + hymem_free_memory (zipEntry->filename); + } + zipEntry->filename = NULL; + if (result == ZIP_ERR_FILE_READ_ERROR) + { + zipFile->pointer = -1; + } + return result; +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_closeZipFile +/** + * Attempt to close the zipfile. + * + * @param[in] portLib the port library + * @param[in] zipFile The zip file to be closed + * + * @return 0 on success + * @return ZIP_ERR_FILE_CLOSE_ERROR if there is an error closing the file + * @return ZIP_ERR_INTERNAL_ERROR if there is an internal error + * +*/ +I_32 VMCALL +zip_closeZipFile (HyPortLibrary * portLib, struct HyZipFile * zipFile) +{ + PORT_ACCESS_FROM_PORT (portLib); +#if defined(HY_NO_THR) + THREAD_ACCESS_FROM_PORT(portLib); +#endif /* HY_NO_THR */ + IDATA fd; + + ENTER (); + + fd = zipFile->fd; + zipFile->fd = -1; + + if (zipFile->cache && zipFile->cachePool) + { + zipCachePool_release (zipFile->cachePool, zipFile->cache); + zipFile->cache = NULL; + } + if ((zipFile->filename) && (zipFile->filename != zipFile->internalFilename)) + { + hymem_free_memory (zipFile->filename); + } + zipFile->filename = NULL; + + if (fd == -1) + { + EXIT (); + return ZIP_ERR_INTERNAL_ERROR; + } + if (hyfile_close (fd)) + { + EXIT (); + return ZIP_ERR_FILE_CLOSE_ERROR; + } + EXIT (); + return 0; +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_establishCache +/** + * Called to set up a cache when a zip file is opened with a cachePool but without a cache, or when the + * current cache is found to be invalid in some way. + * + * @param[in] portLib the port library + * @param[in] zipFile the zip file for which we want to establish a cache + * + * The current cache is marked as invalid such that new instances of zip files + * won't try to use it and an attempt is made to establish a new cache. + * + * @return 0 on success + * @return ZIP_ERR_FILE_READ_ERROR if there is an error reading from zipFile + * @return ZIP_ERR_FILE_OPEN_ERROR if is there is an error opening the file + * @return ZIP_ERR_UNKNOWN_FILE_TYPE if the file type is unknown + * @return ZIP_ERR_UNSUPPORTED_FILE_TYPE if the file type is unsupported + * @return ZIP_ERR_OUT_OF_MEMORY if there is not enough memory to complete this call + * @return ZIP_ERR_INTERNAL_ERROR if there was an internal error +*/ + +I_32 +zip_establishCache (HyPortLibrary * portLib, HyZipFile * zipFile) +{ + PORT_ACCESS_FROM_PORT (portLib); + I_32 result; + I_64 timeStamp, actualFileSize; + IDATA fileSize, filenameLength; + + if (zipFile->cache) + { + if (zipFile->cachePool) + { + /* Whack cache timestamp to keep other people from starting to use it (we will create a new one for them + to start to use instead). Once all the current users of the cache have stopped using it, it will go away */ + zipFile->cache->zipTimeStamp = -2; + zipCachePool_release (zipFile->cachePool, zipFile->cache); + } + zipFile->cache = NULL; + } + if (!zipFile->cachePool) + { + return ZIP_ERR_INTERNAL_ERROR; + } + + /* Check the cachePool for a suitable cache. */ + filenameLength = strlen ((const char *) zipFile->filename); + +// timeStamp = hyfile_lastmod ((const char *) zipFile->filename); +// actualFileSize = hyfile_length ((const char *) zipFile->filename); + { + struct stat st; + tzset (); + stat ((mcSignednessBull)zipFile->filename, &st); + timeStamp = (U_64)st.st_mtime * 1000; + actualFileSize = (I_64) st.st_size; + } + + if ((actualFileSize < 0) || (actualFileSize > HYCONST64 (0x7FFFFFFF))) + { + return ZIP_ERR_INTERNAL_ERROR; + } + fileSize = (IDATA) actualFileSize; + + zipFile->cache = + zipCachePool_findCache (zipFile->cachePool, + (const char *) zipFile->filename, filenameLength, + fileSize, timeStamp); + if (!zipFile->cache) + { + /* Build a new cache. Because caller asked for a cache, fail if we can't provide one */ + zipFile->cache = + zipCache_new (portLib, (char *) zipFile->filename, filenameLength); + if (!zipFile->cache) + return ZIP_ERR_OUT_OF_MEMORY; + + zipFile->cache->zipFileSize = fileSize; + zipFile->cache->zipTimeStamp = timeStamp; + + result = zip_populateCache (portLib, zipFile); + if (result != 0) + { + zipCache_kill (zipFile->cache); + zipFile->cache = NULL; + return result; + } + if (!zipCachePool_addCache (zipFile->cachePool, zipFile->cache)) + { + zipCache_kill (zipFile->cache); + zipFile->cache = NULL; + return ZIP_ERR_OUT_OF_MEMORY; + } + } + return 0; +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_initZipEntry +/** + * Initialize a zip entry. + * + * Should be called before the entry is passed to any other zip support functions + * + * @param[in] portLib the port library + * @param[in] entry the zip entry to init + * + * @return none +*/ + +void +zip_initZipEntry (HyPortLibrary * portLib, HyZipEntry * entry) +{ + memset (entry, 0, sizeof (*entry)); +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_freeZipEntry +/** + * Free any memory associated with a zip entry. + * + * @param[in] portLib the port library + * @param[in] entry the zip entry we are freeing + * + * @return none + * + * @note This does not free the entry itself. +*/ + +void +zip_freeZipEntry (HyPortLibrary * portLib, HyZipEntry * entry) +{ + PORT_ACCESS_FROM_PORT (portLib); + + if ((entry->filename) && (entry->filename != entry->internalFilename)) + { + hymem_free_memory (entry->filename); + } + entry->filename = NULL; + if (entry->extraField) + { + hymem_free_memory (entry->extraField); + entry->extraField = NULL; + } + if (entry->data) + { + hymem_free_memory (entry->data); + entry->data = NULL; + } + if (entry->fileComment) + { + hymem_free_memory (entry->fileComment); + entry->fileComment = NULL; + } +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_getNextZipEntry +/** + * Read the next zip entry at nextEntryPointer into zipEntry. + * + * Any memory held onto by zipEntry may be lost, and therefore + * MUST be freed with @ref zip_freeZipEntry first. + * + * @param[in] portLib the port library + * @param[in] zipFile The zip file being read + * @param[out] zipEntry compressed data is placed here + * @param[in] nextEntryPointer + * + * @return 0 on success + * @return ZIP_ERR_FILE_READ_ERROR if there is an error reading zipFile + * @return ZIP_ERR_FILE_CORRUPT if zipFile is corrupt + * @return ZIP_ERR_NO_MORE_ENTRIES if there are no more entries in zipFile + * @return ZIP_ERR_OUT_OF_MEMORY if there is not enough memory to complete this call + * + * @see zip_freeZipEntry + * +*/ +I_32 +zip_getNextZipEntry (HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * zipEntry, IDATA * nextEntryPointer) +{ + PORT_ACCESS_FROM_PORT (portLib); +#if defined(HY_NO_THR) + THREAD_ACCESS_FROM_PORT(portLib); +#endif /* HY_NO_THR */ + IDATA result; + BOOLEAN retryAllowed = TRUE; + IDATA pointer; + IDATA entryStart; + I_64 seekResult; + + ENTER (); + +retry: + pointer = *nextEntryPointer; + + /* Seek to the entry's position in the file. */ + if (pointer != zipFile->pointer) + { + seekResult = hyfile_seek (zipFile->fd, pointer, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + EXIT (); + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer = (I_32) seekResult; + + if (pointer != zipFile->pointer) + { + zipFile->pointer = -1; + EXIT (); + return ZIP_ERR_FILE_READ_ERROR; + } + } + + /* Read the entry */ + entryStart = *nextEntryPointer; + result = + readZipEntry (portLib, zipFile, zipEntry, NULL, &pointer, &entryStart, + FALSE); + if (result != 0) + { + if (!retryAllowed || (result == ZIP_ERR_NO_MORE_ENTRIES)) + { + EXIT (); + return result; + } + zip_establishCache (portLib, zipFile); + retryAllowed = FALSE; + goto retry; + } + + if (zipFile->cache) + { + /* Validity check: look up filename in the cache... */ + result = + (IDATA) zipCache_findElement (zipFile->cache, + (const char *) zipEntry->filename, + FALSE); + if (result != entryStart) + { + if (result >= zipFile->cache->startCentralDir) + { + /* ! Cache contents are not valid. Invalidate it and make a new one */ + if (!retryAllowed) + { + EXIT (); + return ZIP_ERR_FILE_CORRUPT; /* should never happen! */ + } + result = zip_establishCache (portLib, zipFile); + if (result) + { + /* (silently start operating without a cache if we couldn't make a new one) */ + } + else + { + retryAllowed = FALSE; + goto retry; + } + } + else + { + /* We know that the central header for this entry contains info that the + local header is missing (comment length and/or uncompressed size) */ + zipEntry->fileCommentPointer = -1; + } + } + } + + *nextEntryPointer = pointer; + EXIT (); + return 0; +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_getZipEntry +/** + * Attempt to find and read the zip entry corresponding to filename. + * If found, read the entry into the parameter entry. + * + * If an uncached entry is found, the filename field will be filled in. This + * memory will have to be freed with @ref zip_freeZipEntry. + * + * @param[in] portLib the port library + * @param[in] zipFile the file being read from + * @param[out] entry the zip entry found in zipFile is read to here + * @param[in] filename the name of the file that corresponds to entry + * @param[in] findDirectory when true, match a directory even if filename does not end in '/'. + * Note findDirectory is only supported (for the JCL) when there is a cache + * + * @return 0 on success or one of the following: + * @return ZIP_ERR_FILE_READ_ERROR if there is an error reading from zipFile + * @return ZIP_ERR_FILE_CORRUPT if zipFile is corrupt + * @return ZIP_ERR_ENTRY_NOT_FOUND if a zip entry with name filename was not found + * @return ZIP_ERR_OUT_OF_MEMORY if there is not enough memory to complete this call + * + * @see zip_freeZipEntry +*/ + +I_32 +zip_getZipEntry (HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, const char *filename, + BOOLEAN findDirectory) +{ + PORT_ACCESS_FROM_PORT (portLib); +#if defined(HY_NO_THR) + THREAD_ACCESS_FROM_PORT(portLib); +#endif /* HY_NO_THR */ + IDATA result, position; + BOOLEAN retryAllowed = TRUE; + I_64 seekResult; + + ENTER (); + +retry: + if (zipFile->cache) + { + /* Look up filename in the cache. */ + position = + (IDATA) zipCache_findElement (zipFile->cache, filename, + findDirectory); + if (position == -1) + { + /* Note: we assume the cache is still valid here */ + EXIT (); + return ZIP_ERR_ENTRY_NOT_FOUND; + } + + /* Seek to the entry's position in the file. */ + if (zipFile->pointer != position) + { + seekResult = hyfile_seek (zipFile->fd, position, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + EXIT (); + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer = (I_32) seekResult; + + if (zipFile->pointer != position) + { + zipFile->pointer = -1; + EXIT (); + return ZIP_ERR_FILE_READ_ERROR; + } + } + + /* Read the entry */ + result = + readZipEntry (portLib, zipFile, entry, filename, NULL, NULL, + findDirectory); + if (result != 0) + { + if (!retryAllowed) + { + EXIT (); + return result; + } + result = zip_establishCache (portLib, zipFile); /* invalidate existing cache */ + if (result) + { + EXIT (); + return result; + } + retryAllowed = FALSE; + goto retry; + } + EXIT (); + return 0; + } + else + { + /* Uh oh -- random access without a cache (SLOW!) */ + position = 0; + zip_resetZipFile (PORTLIB, zipFile, &position); + while (TRUE) + { + + if (zipFile->pointer != position) + { + seekResult = hyfile_seek (zipFile->fd, position, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + EXIT (); + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer = (I_32) seekResult; + + if (zipFile->pointer != position) + { + zipFile->pointer = -1; + EXIT (); + return ZIP_ERR_FILE_READ_ERROR; + } + } + + result = + readZipEntry (portLib, zipFile, entry, NULL, &position, NULL, + FALSE); + if (result || !strcmp ((const char *) entry->filename, filename)) + { + EXIT (); + return result; + } + + /* No match. Reset for next entry */ + zip_freeZipEntry (portLib, entry); + zip_initZipEntry (portLib, entry); + } + } +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_getZipEntryData +/** + * Attempt to read and uncompress the data for the zip entry entry. + * + * If buffer is non-NULL it is used, but not explicitly held onto by the entry. + * If buffer is NULL, memory is allocated and held onto by the entry, and thus + * should later be freed with @ref zip_freeZipEntry. + * + * @param[in] portLib the port library + * @param[in] zipFile the zip file being read from. + * @param[in,out] entry the zip entry + * @param[in] buffer may or may not be NULL + * @param[in] bufferSize + + * @return 0 on success + * @return ZIP_ERR_FILE_READ_ERROR if there is an error reading from zipEntry + * @return ZIP_ERR_FILE_CORRUPT if zipFile is corrupt + * @return ZIP_ERR_ENTRY_NOT_FOUND if entry is not found + * @return ZIP_ERR_OUT_OF_MEMORY if there is not enough memory to complete this call + * @return ZIP_ERR_BUFFER_TOO_SMALL if buffer is too small to hold the comment for zipFile + * + * @see zip_freeZipEntry + * +*/ +I_32 +zip_getZipEntryData (HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, U_8 * buffer, U_32 bufferSize) +{ + PORT_ACCESS_FROM_PORT (portLib); +#if defined(HY_NO_THR) + THREAD_ACCESS_FROM_PORT(portLib); +#endif /* HY_NO_THR */ + + I_32 result; + U_8 *dataBuffer; + struct workBuffer wb; + I_64 seekResult; + + ENTER (); + + wb.portLib = portLib; + wb.bufferStart = wb.bufferEnd = wb.currentAlloc = 0; + + if (buffer) + { + if (bufferSize < entry->uncompressedSize) + { + EXIT (); + return ZIP_ERR_BUFFER_TOO_SMALL; + } + dataBuffer = buffer; + } + else + { + /* Note that this is the first zalloc. This memory must be available to the calling method and is freed explicitly in zip_freeZipEntry. */ + /* Note that other allocs freed in zip_freeZipEntry are not alloc'd using zalloc */ + dataBuffer = zdataalloc (&wb, 1, entry->uncompressedSize); + if (!dataBuffer) + { + EXIT (); + return ZIP_ERR_OUT_OF_MEMORY; + } + entry->data = dataBuffer; + } + + if (entry->compressionMethod == ZIP_CM_Stored) + { + /* No compression - just read the data in. */ + if (zipFile->pointer != entry->dataPointer) + { + seekResult = + hyfile_seek (zipFile->fd, entry->dataPointer, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer = (I_32) seekResult; + + if (zipFile->pointer != entry->dataPointer) + { + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + } + result = hyfile_read (zipFile->fd, dataBuffer, entry->compressedSize); + if (result != (I_32) entry->compressedSize) + { + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer += result; + EXIT (); + return 0; + } + + if (entry->compressionMethod == ZIP_CM_Deflated) + { + U_8 *readBuffer; + + /* Ensure that the library is loaded. */ + if (checkZipLibrary (portLib)) + { + result = ZIP_ERR_UNSUPPORTED_FILE_TYPE; + goto finished; + } + + /* Read the file contents. */ + readBuffer = zdataalloc (&wb, 1, entry->compressedSize); + if (!readBuffer) + { + result = ZIP_ERR_OUT_OF_MEMORY; + goto finished; + } + if (zipFile->pointer != entry->dataPointer) + { + seekResult = + hyfile_seek (zipFile->fd, entry->dataPointer, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + zdatafree (&wb, readBuffer); + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer = (I_32) seekResult; + + if (zipFile->pointer != entry->dataPointer) + { + zdatafree (&wb, readBuffer); + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + } + if (hyfile_read (zipFile->fd, readBuffer, entry->compressedSize) != + (I_32) entry->compressedSize) + { + zdatafree (&wb, readBuffer); + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer += (I_32) entry->compressedSize; + + /* Deflate the data. */ + result = + inflateData (&wb, readBuffer, entry->compressedSize, dataBuffer, + entry->uncompressedSize); + zdatafree (&wb, readBuffer); + if (result) + goto finished; + EXIT (); + return 0; + } + + /* Whatever this is, we can't decompress it */ + result = ZIP_ERR_UNSUPPORTED_FILE_TYPE; + +finished: + if (!buffer) + { + entry->data = NULL; + zdatafree (&wb, dataBuffer); + } + if (result == ZIP_ERR_FILE_READ_ERROR) + { + zipFile->pointer = -1; + } + EXIT (); + return result; +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_getZipEntryExtraField +/** + * Read the extra field of entry from the zip file filename. + * + * buffer is used if non-NULL, but is not held onto by entry. + * + * If buffer is NULL, memory is allocated and held onto by entry, and MUST be freed later with + * @ref zip_freeZipEntry. + * + * @param[in] portLib the port library + * @param[in] zipFile the zip file being read from. + * @param[in,out] entry the zip entry concerned + * @param[in] buffer may or may not be NULL + * @param[in] bufferSize + * + * @return 0 on success or one of the following: + * @return ZIP_ERR_FILE_READ_ERROR if there is an error reading from zipFile + * @return ZIP_ERR_FILE_CORRUPT if zipFile is corrupt + * @return ZIP_ERR_OUT_OF_MEMORY if there is not enough memory to complete this call + * @return ZIP_ERR_BUFFER_TOO_SMALL if the buffer was non-Null but not large enough to hold the contents of entry + * + * @see zip_freeZipEntry +*/ +I_32 +zip_getZipEntryExtraField (HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, U_8 * buffer, U_32 bufferSize) +{ + PORT_ACCESS_FROM_PORT (portLib); +#if defined(HY_NO_THR) + THREAD_ACCESS_FROM_PORT(portLib); +#endif /* HY_NO_THR */ + + I_32 result; + U_8 *extraFieldBuffer; + I_64 seekResult; + + ENTER (); + + if (entry->extraFieldLength == 0) + { + EXIT (); + return 0; + } + + if (buffer) + { + if (bufferSize < entry->extraFieldLength) + { + EXIT (); + return ZIP_ERR_BUFFER_TOO_SMALL; + } + extraFieldBuffer = buffer; + } + else + { + extraFieldBuffer = hymem_allocate_memory (entry->extraFieldLength); + if (!extraFieldBuffer) + { + EXIT (); + return ZIP_ERR_OUT_OF_MEMORY; + } + entry->extraField = extraFieldBuffer; + } + + if (zipFile->pointer != entry->extraFieldPointer) + { + seekResult = + hyfile_seek (zipFile->fd, entry->extraFieldPointer, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer = (I_32) seekResult; + if (zipFile->pointer != entry->extraFieldPointer) + { + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + } + result = + hyfile_read (zipFile->fd, extraFieldBuffer, entry->extraFieldLength); + if (result != (I_32) entry->extraFieldLength) + { + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer += result; + EXIT (); + return 0; + +finished: + if (!buffer) + { + entry->extraField = NULL; + hymem_free_memory (extraFieldBuffer); + } + if (result == ZIP_ERR_FILE_READ_ERROR) + zipFile->pointer = -1; + EXIT (); + return result; + +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_getZipEntryFilename + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_getZipEntryComment +/** + * Read the file comment for entry. + * + * If buffer is non-NULL, it is used, but not held onto by entry. + * + * If buffer is NULL, memory is allocated and + * held onto by entry, and thus should later be freed with @ref zip_freeZipEntry. + * + * @param[in] portLib the port library + * @param[in] zipFile the zip file concerned + * @param[in] entry the entry who's comment we want + * @param[in] buffer may or may not be NULL + * @param[in] bufferSize + + * @return 0 on success or one of the following + * @return ZIP_ERR_FILE_READ_ERROR if there is an error reading the file comment from zipEntry + * @return ZIP_ERR_FILE_CORRUPT if zipFile is corrupt + * @return ZIP_ERR_ENTRY_NOT_FOUND if entry is not found + * @return ZIP_ERR_OUT_OF_MEMORY if there is not enough memory to complete this call + * @return ZIP_ERR_BUFFER_TOO_SMALL if buffer is too small to hold the comment for zipFile +*/ + +I_32 +zip_getZipEntryComment (HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, U_8 * buffer, U_32 bufferSize) +{ + PORT_ACCESS_FROM_PORT (portLib); +#if defined(HY_NO_THR) + THREAD_ACCESS_FROM_PORT(portLib); +#endif /* HY_NO_THR */ + + I_32 result; + U_8 *fileCommentBuffer; + I_64 seekResult; + + ENTER (); + + if (entry->fileCommentLength == 0) + { + if (entry->fileCommentPointer == -1) + { + /* TODO: we don't know where the comment is (or even if there is one)! This only happens if you're running + without a cache, so too bad for now */ + } + EXIT (); + return 0; + } + + if (buffer) + { + if (bufferSize < entry->fileCommentLength) + { + EXIT (); + return ZIP_ERR_BUFFER_TOO_SMALL; + } + fileCommentBuffer = buffer; + } + else + { + fileCommentBuffer = hymem_allocate_memory (entry->fileCommentLength); + if (!fileCommentBuffer) + { + EXIT (); + return ZIP_ERR_OUT_OF_MEMORY; + } + entry->fileComment = fileCommentBuffer; + } + + if (zipFile->pointer != entry->fileCommentPointer) + { + seekResult = + hyfile_seek (zipFile->fd, entry->fileCommentPointer, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer = (I_32) seekResult; + + if (zipFile->pointer != entry->fileCommentPointer) + { + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + } + result = + hyfile_read (zipFile->fd, fileCommentBuffer, entry->fileCommentLength); + if (result != (I_32) entry->fileCommentLength) + { + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + zipFile->pointer += result; + EXIT (); + return 0; + +finished: + if (!buffer) + { + entry->fileComment = NULL; + hymem_free_memory (fileCommentBuffer); + } + if (result == ZIP_ERR_FILE_READ_ERROR) + { + zipFile->pointer = -1; + } + EXIT (); + return result; + +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_openZipFile +/** + * Attempt to open a zip file. + * + * If the cache pool is non-NULL, the cachePool will be used to find a suitable cache, and if none can be found it will create one and add it to cachePool. + * Zip support is responsible for managing the lifetime of the cache. + * + * @note If cachePool is NULL, zipFile will be opened without a cache. + * + * @param[in] portLib the port library + * @param[in] filename the zip file to open + * @param[out] zipFile the zip file structure to populate + * @param[in] cachePool the cache pool + * + * @return 0 on success + * @return ZIP_ERR_FILE_OPEN_ERROR if is there is an error opening the file + * @return ZIP_ERR_FILE_READ_ERROR if there is an error reading the file + * @return ZIP_ERR_FILE_CORRUPT if the file is corrupt + * @return ZIP_ERR_UNKNOWN_FILE_TYPE if the file type is not known + * @return ZIP_ERR_UNSUPPORTED_FILE_TYPE if the file type is unsupported + * @return ZIP_ERR_OUT_OF_MEMORY if we are out of memory +*/ +I_32 +zip_openZipFile (HyPortLibrary * portLib, char *filename, HyZipFile * zipFile, + HyZipCachePool * cachePool) +{ + PORT_ACCESS_FROM_PORT (portLib); +#if defined(HY_NO_THR) + THREAD_ACCESS_FROM_PORT(portLib); +#endif /* HY_NO_THR */ + + IDATA fd = -1; + I_32 result = 0; + I_64 seekResult; + U_8 buffer[4]; + I_32 len; + + ENTER (); + + len = strlen (filename); + zipFile->fd = -1; + zipFile->type = ZIP_Unknown; + zipFile->cache = NULL; + zipFile->cachePool = NULL; + zipFile->pointer = -1; + /* Allocate space for filename */ + if (len >= ZIP_INTERNAL_MAX) + { + zipFile->filename = hymem_allocate_memory (len + 1); + if (!zipFile->filename) + { + EXIT (); + return ZIP_ERR_OUT_OF_MEMORY; + } + } + else + { + zipFile->filename = zipFile->internalFilename; + } + + strcpy ((char *) zipFile->filename, filename); + + fd = hyfile_open (filename, HyOpenRead, 0); + if (fd == -1) + { + result = ZIP_ERR_FILE_OPEN_ERROR; + goto finished; + } + + if (hyfile_read (fd, buffer, 4) != 4) + { + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + + if ((buffer[0] == 'P') && (buffer[1] == 'K')) + { + /* If not the central header or local file header, its corrupt */ + if (! + ((buffer[2] == 1 && buffer[3] == 2) + || (buffer[2] == 3 && buffer[3] == 4))) + { + result = ZIP_ERR_FILE_CORRUPT; + goto finished; + } + /* PKZIP file. Back up the pointer to the beginning. */ + seekResult = hyfile_seek (fd, 0, HySeekSet); + if (seekResult != 0) + { + result = ZIP_ERR_FILE_READ_ERROR; + goto finished; + } + + zipFile->fd = fd; + zipFile->type = ZIP_PKZIP; + zipFile->pointer = 0; + } + + if ((buffer[0] == 0x1F) && (buffer[1] == 0x8B)) + { + /* GZIP - currently unsupported. + zipFile->fd = fd; + zipFile->type = ZIP_GZIP; + zipFile->pointer = 2; + */ + result = ZIP_ERR_UNSUPPORTED_FILE_TYPE; + goto finished; + } + + if (zipFile->type == ZIP_Unknown) + { + result = ZIP_ERR_UNKNOWN_FILE_TYPE; + goto finished; + } + + result = 0; + + if (cachePool) + { + zipFile->cachePool = cachePool; + result = zip_establishCache (portLib, zipFile); + } + +finished: + if (result == 0) + { + zipFile->fd = fd; + EXIT (); + return 0; + } + if (fd != -1) + { + hyfile_close (fd); + } + if ((zipFile->filename) && (zipFile->filename != zipFile->internalFilename)) + { + hymem_free_memory (zipFile->filename); + } + zipFile->filename = NULL; + EXIT (); + return result; +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_resetZipFile +/** + * Reset nextEntryPointer to the first entry in the file. + * + * @param[in] portLib the port library + * @param[in] zipFile the zip being read from + * @param[out] nextEntryPointer will be reset to the first entry in the file + * + * @return none + * + * +*/ +void +zip_resetZipFile (HyPortLibrary * portLib, HyZipFile * zipFile, + IDATA * nextEntryPointer) +{ + *nextEntryPointer = 0; + if (zipFile) + { + if (zipFile->cache) + *nextEntryPointer = zipFile->cache->startCentralDir; + else + { + I_32 result; + HyZipCentralEnd endEntry; + result = scanForCentralEnd (portLib, zipFile, &endEntry); + if (result != 0) + return; + *nextEntryPointer = (IDATA) ((UDATA) endEntry.dirOffset); + } + } +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zip_getZipEntryFromOffset +/** + * Attempt to read a zip entry at offset from the zip file provided. + * If found, read into entry. + * + * @note If an uncached entry is found, the filename field will be filled in. This + * memory MUST be freed with @ref zip_freeZipEntry. + * + * @param[in] portLib the port library + * @param[in] zipFile the zip file being read + * @param[in] offset the offset into the zipFile of the desired zip entry + * @param[out] entry the compressed data + * + * @return 0 on success + * @return ZIP_ERR_FILE_READ_ERROR if there is an error reading from @ref zipFile + * @return ZIP_ERR_FILE_CORRUPT if @ref zipFile is corrupt + * @return ZIP_ERR_ENTRY_NOT_FOUND if the entry is not found + * @return ZIP_ERR_OUT_OF_MEMORY if there is not enough memory to complete this call + * + * @see zip_freeZipEntry +*/ +I_32 +zip_getZipEntryFromOffset (HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, IDATA offset) +{ + PORT_ACCESS_FROM_PORT (portLib); +#if defined(HY_NO_THR) + THREAD_ACCESS_FROM_PORT(portLib); +#endif /* HY_NO_THR */ + I_32 result; + I_64 seekResult; + + ENTER (); + + if (zipFile->pointer != offset) + { + seekResult = hyfile_seek (zipFile->fd, offset, HySeekSet); + if ((seekResult < 0) || (seekResult > HYCONST64 (0x7FFFFFFF))) + { + zipFile->pointer = -1; + EXIT (); + return ZIP_ERR_FILE_READ_ERROR; + } + zipFile->pointer = (I_32) seekResult; + if (zipFile->pointer != offset) + { + zipFile->pointer = -1; + EXIT (); + return ZIP_ERR_FILE_READ_ERROR; + } + } + + result = readZipEntry (portLib, zipFile, entry, NULL, NULL, NULL, FALSE); + EXIT (); + return result; +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zdataalloc +/* + cached alloc management for zip_getZipEntryData. +*/ +void * +zdataalloc (void *opaque, U_32 items, U_32 size) +{ + UDATA *returnVal = 0; + U_32 byteSize = items * size; + U_32 allocSize = WORK_BUFFER_SIZE; + struct workBuffer *wb = (struct workBuffer *) opaque; + + PORT_ACCESS_FROM_PORT (wb->portLib); + + /* Round to UDATA multiple */ + byteSize = (byteSize + (sizeof (UDATA) - 1)) & ~(sizeof (UDATA) - 1); + + if (wb->bufferStart == 0) + { + if (byteSize > WORK_BUFFER_SIZE) + { + allocSize = byteSize; + } + wb->bufferStart = hymem_allocate_memory (allocSize); + if (wb->bufferStart) + { + wb->bufferEnd = (UDATA *) ((UDATA) wb->bufferStart + allocSize); + wb->currentAlloc = wb->bufferStart; + wb->cntr = 0; + } + } + + if ((wb->bufferStart == 0) + || (((UDATA) wb->currentAlloc + byteSize) > (UDATA) wb->bufferEnd)) + { + returnVal = hymem_allocate_memory (byteSize); + } + else + { + ++(wb->cntr); + returnVal = wb->currentAlloc; + wb->currentAlloc = (UDATA *) ((UDATA) wb->currentAlloc + byteSize); + } + return returnVal; +} + +#undef CDEV_CURRENT_FUNCTION + +#define CDEV_CURRENT_FUNCTION zdatafree +/* + cached alloc management for zip_getZipEntryData. +*/ +void +zdatafree (void *opaque, void *address) +{ + struct workBuffer *wb = (struct workBuffer *) opaque; + + PORT_ACCESS_FROM_PORT (wb->portLib); + + if ((address < (void *) wb->bufferStart) + || (address >= (void *) wb->bufferEnd)) + { + hymem_free_memory (address); + } + else if (--(wb->cntr) == 0) + { + hymem_free_memory (wb->bufferStart); + wb->bufferStart = wb->bufferEnd = wb->currentAlloc = 0; + } + +} + +#undef CDEV_CURRENT_FUNCTION diff --git a/archive/src/main/native/zipsup.h b/archive/src/main/native/zipsup.h new file mode 100644 index 0000000..adc086a --- /dev/null +++ b/archive/src/main/native/zipsup.h @@ -0,0 +1,250 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** +* Zip Support Header +*/ + +#if !defined(ZIPSUP_H) +#define ZIPSUP_H +#if defined(__cplusplus) +extern "C" +{ +#endif +#include "hy2sie.h" + +#include <stdlib.h> +#include <unistd.h> +#include <memory.h> + +#include "zlib.h" + + +// Contents from Harmony's inflater.h was put here: +// +typedef struct JCLZipStream +{ + U_8 *inaddr; + U_8 *dict; + z_stream *stream; +} JCLZipStream; + + +typedef struct HyZipCachePool HyZipCachePool; + +HyZipCachePool * +zipsup_GetZipCachePool(HyPortLibrary * portLib); + + +#define HY_ZIP_DLL_NAME "hyzlib" + +#define ZIP_INTERNAL_MAX 80 +#define ZIP_CM_Reduced1 2 +#define ZIP_Unknown 0 +#define ZIP_GZIP 2 +#define ZIP_ERR_OUT_OF_MEMORY -3 +#define ZIP_ERR_FILE_CORRUPT -6 +#define ZIP_ERR_INTERNAL_ERROR -11 +#define ZIP_CM_Imploded 6 +#define ZIP_CM_Reduced4 5 +#define ZIP_CM_Shrunk 1 +#define ZIP_CM_Reduced2 3 +#define ZIP_ERR_FILE_READ_ERROR -1 +#define ZIP_CentralHeader 0x2014B50 +#define ZIP_ERR_FILE_CLOSE_ERROR -10 +#define ZIP_ERR_BUFFER_TOO_SMALL -7 +#define ZIP_CM_Reduced3 4 +#define ZIP_CM_Deflated 8 +#define ZIP_LocalHeader 0x4034B50 +#define ZIP_CM_Tokenized 7 +#define ZIP_PKZIP 1 +#define ZIP_CM_Stored 0 +#define ZIP_ERR_UNSUPPORTED_FILE_TYPE -5 +#define ZIP_ERR_NO_MORE_ENTRIES -2 +#define ZIP_CentralEnd 0x6054B50 +#define ZIP_ERR_FILE_OPEN_ERROR -9 +#define ZIP_ERR_UNKNOWN_FILE_TYPE -4 +#define ZIP_ERR_ENTRY_NOT_FOUND -8 +#define ZIP_DataDescriptor 0x8074B50 + + typedef struct HyZipCache + { + U_8 *zipFileName; + IDATA zipFileSize; + I_64 zipTimeStamp; + IDATA startCentralDir; + HyPortLibrary *portLib; + void *cachePool; + void *cachePoolEntry; + } HyZipCache; + + + typedef struct HyZipCentralEnd + { + U_16 diskNumber; + U_16 dirStartDisk; + U_16 thisDiskEntries; + U_16 totalEntries; + U_32 dirSize; + U_32 dirOffset; + U_16 commentLength; + char _hypadding0012[2]; /* 2 bytes of automatic padding */ + U_8 *comment; + } HyZipCentralEnd; + + + typedef struct HyZipDataDescriptor + { + U_32 crc32; + U_32 compressedSize; + U_32 uncompressedSize; + } HyZipDataDescriptor; + + + typedef struct HyZipEntry + { + U_8 *data; + U_8 *filename; + U_8 *extraField; + U_8 *fileComment; + I_32 dataPointer; + I_32 filenamePointer; + I_32 extraFieldPointer; + I_32 fileCommentPointer; + U_32 compressedSize; + U_32 uncompressedSize; + U_32 crc32; + U_16 filenameLength; + U_16 extraFieldLength; + U_16 fileCommentLength; + U_16 internalAttributes; + U_16 versionCreated; + U_16 versionNeeded; + U_16 flags; + U_16 compressionMethod; + U_16 lastModTime; + U_16 lastModDate; + U_8 internalFilename[80]; + } HyZipEntry; + + + typedef struct HyZipFile + { + U_8 *filename; + struct HyZipCache *cache; + void *cachePool; + I_32 fd; + I_32 pointer; + U_8 internalFilename[80]; + U_8 type; + char _hypadding0065[3]; /* 3 bytes of automatic padding */ + } HyZipFile; + + + +// Contents from Harmony's zip.h were put in java_util_zip_ZipFile.c +// and here: +typedef struct JCLZipFile +{ + struct JCLZipFile *last; + struct JCLZipFile *next; + HyZipFile hyZipFile; +} JCLZipFile; + + + + +#include "hymutex.h" +extern MUTEX zip_GlobalMutex; + + + +#define jclmem_allocate_memory(env, byteCount) sieb_malloc(env, byteCount) +#define jclmem_free_memory(env, pointer) sieb_free(env, pointer) + + +/* HySourceZipSupport*/ + extern HY_CFUNC I_32 zip_getZipEntryData + PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, U_8 * buffer, U_32 bufferSize)); + extern HY_CFUNC I_32 zip_getZipEntryFromOffset + PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, IDATA offset)); + extern HY_CFUNC I_32 zip_establishCache + PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile)); + extern HY_CFUNC void zip_resetZipFile + PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + IDATA * nextEntryPointer)); + extern HY_CFUNC I_32 zip_getNextZipEntry + PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * zipEntry, IDATA * nextEntryPointer)); + extern HY_CFUNC I_32 zip_getZipEntry + PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, const char *filename, + BOOLEAN findDirectory)); + extern HY_CFUNC I_32 zip_getZipEntryExtraField + PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, U_8 * buffer, U_32 bufferSize)); + extern HY_CFUNC void zip_initZipEntry + PROTOTYPE ((HyPortLibrary * portLib, HyZipEntry * entry)); + extern HY_CFUNC I_32 zip_openZipFile + PROTOTYPE ((HyPortLibrary * portLib, char *filename, HyZipFile * zipFile, + HyZipCachePool * cachePool)); + extern HY_CFUNC void zip_freeZipEntry + PROTOTYPE ((HyPortLibrary * portLib, HyZipEntry * entry)); + struct HyZipFile; + extern HY_CFUNC I_32 VMCALL zip_closeZipFile + PROTOTYPE ((HyPortLibrary * portLib, struct HyZipFile * zipFile)); + extern HY_CFUNC I_32 zip_getZipEntryComment + PROTOTYPE ((HyPortLibrary * portLib, HyZipFile * zipFile, + HyZipEntry * entry, U_8 * buffer, U_32 bufferSize)); +/* HySourceZipCache*/ + extern HY_CFUNC UDATA zipCache_findElement + PROTOTYPE ((HyZipCache * zipCache, const char *elementName, + BOOLEAN searchDirList)); + extern HY_CFUNC void zipCache_kill PROTOTYPE ((HyZipCache * zipCache)); + extern HY_CFUNC IDATA zipCache_enumGetDirName + PROTOTYPE ((void *handle, char *nameBuf, UDATA nameBufSize)); + extern HY_CFUNC HyZipCache *zipCache_new + PROTOTYPE ((HyPortLibrary * portLib, char *zipName, IDATA zipNameLength)); + extern HY_CFUNC IDATA zipCache_enumNew + PROTOTYPE ((HyZipCache * zipCache, char *directoryName, void **handle)); + extern HY_CFUNC IDATA zipCache_enumElement + PROTOTYPE ((void *handle, char *nameBuf, UDATA nameBufSize, + UDATA * offset)); + extern HY_CFUNC void zipCache_enumKill PROTOTYPE ((void *handle)); + extern HY_CFUNC BOOLEAN zipCache_addElement + PROTOTYPE ((HyZipCache * zipCache, char *elementName, + UDATA elementOffset)); +/* HySourceZipCachePool*/ + extern HY_CFUNC BOOLEAN zipCachePool_release + PROTOTYPE ((HyZipCachePool * zcp, HyZipCache * zipCache)); + extern HY_CFUNC void zipCachePool_kill PROTOTYPE ((HyZipCachePool * zcp)); + extern HY_CFUNC HyZipCache *zipCachePool_findCache + PROTOTYPE ((HyZipCachePool * zcp, char const *zipFileName, + IDATA zipFileNameLength, IDATA zipFileSize, + I_64 zipTimeStamp)); + extern HY_CFUNC HyZipCachePool *zipCachePool_new + PROTOTYPE ((HyPortLibrary * portLib)); + extern HY_CFUNC BOOLEAN zipCachePool_addCache + PROTOTYPE ((HyZipCachePool * zcp, HyZipCache * zipCache)); + extern HY_CFUNC BOOLEAN zipCachePool_addRef + PROTOTYPE ((HyZipCachePool * zcp, HyZipCache * zipCache)); +#if defined(__cplusplus) +} +#endif +#endif /* ZIPSUP_H */ diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/AllTests.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/AllTests.java new file mode 100644 index 0000000..13fe019 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/AllTests.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Test suite for java.util.jar package. + */ +public class AllTests { + + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static Test suite() { + TestSuite suite = tests.TestSuiteFactory.createTestSuite( + "Suite org.apache.harmony.archive.tests.java.util.jar"); + suite.addTestSuite(AttributesNameTest.class); + suite.addTestSuite(AttributesTest.class); + suite.addTestSuite(JarEntryTest.class); + suite.addTestSuite(JarExceptionTest.class); + suite.addTestSuite(JarExecTest.class); + suite.addTestSuite(JarFileTest.class); + suite.addTestSuite(JarInputStreamTest.class); + suite.addTestSuite(JarOutputStreamTest.class); + suite.addTestSuite(ManifestTest.class); + suite.addTestSuite(Pack200Test.class); + suite.addTestSuite(Pack200PackerTest.class); + suite.addTestSuite(Pack200UnpackerTest.class); + suite.addTestSuite(ZipExecTest.class); + return suite; + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/AttributesNameTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/AttributesNameTest.java new file mode 100644 index 0000000..067bf49 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/AttributesNameTest.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.util.jar.Attributes; +import junit.framework.TestCase; + +@TestTargetClass(Attributes.Name.class) +public class AttributesNameTest extends TestCase { + + /** + * @tests java.util.jar.Attributes.Name#Name(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Name", + args = {java.lang.String.class} + ) + public void test_AttributesName_Constructor() { + // Regression for HARMONY-85 + try { + new Attributes.Name( + "01234567890123456789012345678901234567890123456789012345678901234567890"); + fail("Assert 0: should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + + try { + new Attributes.Name((String) null); + fail("NullPointerException expected"); + } catch (NullPointerException ee) { + // expected + } + + assertNotNull(new Attributes.Name("Attr")); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + Attributes.Name attr1 = new Attributes.Name("Attr"); + Attributes.Name attr2 = new Attributes.Name("Attr"); + + assertTrue(attr1.equals(attr2)); + attr2 = new Attributes.Name("Attr1"); + assertFalse(attr1.equals(attr2)); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + Attributes.Name attr1 = new Attributes.Name("Attr1"); + Attributes.Name attr2 = new Attributes.Name("Attr2"); + + assertNotSame(attr1.hashCode(), attr2.hashCode()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toString", + args = {} + ) + public void test_toString() { + String str1 = "Attr1"; + String str2 = "Attr2"; + Attributes.Name attr1 = new Attributes.Name(str1); + Attributes.Name attr2 = new Attributes.Name("Attr2"); + + assertTrue(attr1.toString().equals(str1)); + assertTrue(attr2.toString().equals(str2)); + assertFalse(attr2.toString().equals(str1)); + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/AttributesTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/AttributesTest.java new file mode 100644 index 0000000..0a8b037 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/AttributesTest.java @@ -0,0 +1,520 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.jar.Attributes; +import junit.framework.TestCase; + +@TestTargetClass(Attributes.class) +public class AttributesTest extends TestCase { + private Attributes a; + + @Override + protected void setUp() { + a = new Attributes(); + a.putValue("1", "one"); + a.putValue("2", "two"); + a.putValue("3", "three"); + a.putValue("4", "four"); + } + + /** + * @tests java.util.jar.Attributes#Attributes(java.util.jar.Attributes) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Attributes", + args = {java.util.jar.Attributes.class} + ) + public void test_ConstructorLjava_util_jar_Attributes() { + Attributes a2 = new Attributes(a); + assertEquals(a, a2); + a.putValue("1", "one(1)"); + assertTrue("equal", !a.equals(a2)); + } + + /** + * @tests java.util.jar.Attributes#clear() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clear", + args = {} + ) + public void test_clear() { + a.clear(); + assertNull("a) All entries should be null after clear", a.get("1")); + assertNull("b) All entries should be null after clear", a.get("2")); + assertNull("c) All entries should be null after clear", a.get("3")); + assertNull("d) All entries should be null after clear", a.get("4")); + assertTrue("Should not contain any keys", !a.containsKey("1")); + } + + /** + * @tests java.util.jar.Attributes#containsKey(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "containsKey", + args = {java.lang.Object.class} + ) + public void test_containsKeyLjava_lang_Object() { + assertTrue("a) Should have returned false", !a.containsKey(new Integer( + 1))); + assertTrue("b) Should have returned false", !a.containsKey("0")); + assertTrue("Should have returned true", a + .containsKey(new Attributes.Name("1"))); + } + + /** + * @tests java.util.jar.Attributes#containsValue(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "containsValue", + args = {java.lang.Object.class} + ) + public void test_containsValueLjava_lang_Object() { + assertTrue("Should have returned false", !a.containsValue("One")); + assertTrue("Should have returned true", a.containsValue("one")); + } + + /** + * @tests java.util.jar.Attributes#entrySet() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "entrySet", + args = {} + ) + public void test_entrySet() { + Set<Map.Entry<Object, Object>> entrySet = a.entrySet(); + Set<Object> keySet = new HashSet<Object>(); + Set<Object> valueSet = new HashSet<Object>(); + Iterator<?> i; + assertEquals(4, entrySet.size()); + i = entrySet.iterator(); + while (i.hasNext()) { + java.util.Map.Entry<?, ?> e; + e = (Map.Entry<?, ?>) i.next(); + keySet.add(e.getKey()); + valueSet.add(e.getValue()); + } + assertTrue("a) Should contain entry", valueSet.contains("one")); + assertTrue("b) Should contain entry", valueSet.contains("two")); + assertTrue("c) Should contain entry", valueSet.contains("three")); + assertTrue("d) Should contain entry", valueSet.contains("four")); + assertTrue("a) Should contain key", keySet + .contains(new Attributes.Name("1"))); + assertTrue("b) Should contain key", keySet + .contains(new Attributes.Name("2"))); + assertTrue("c) Should contain key", keySet + .contains(new Attributes.Name("3"))); + assertTrue("d) Should contain key", keySet + .contains(new Attributes.Name("4"))); + } + + /** + * @tests java.util.jar.Attributes#get(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getValue", + args = {java.lang.String.class} + ) + public void test_getLjava_lang_Object() { + assertEquals("a) Incorrect value returned", "one", a.getValue("1")); + assertNull("b) Incorrect value returned", a.getValue("0")); + + try { + a.getValue("IllegalArgumentException expected"); + } catch (IllegalArgumentException ee) { + // expected + } + } + + /** + * @tests java.util.jar.Attributes#isEmpty() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isEmpty", + args = {} + ) + public void test_isEmpty() { + assertTrue("Should not be empty", !a.isEmpty()); + a.clear(); + assertTrue("a) Should be empty", a.isEmpty()); + a = new Attributes(); + assertTrue("b) Should be empty", a.isEmpty()); + } + + /** + * @tests java.util.jar.Attributes#keySet() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "keySet", + args = {} + ) + public void test_keySet() { + Set<?> s = a.keySet(); + assertEquals(4, s.size()); + assertTrue("a) Should contain entry", s.contains(new Attributes.Name( + "1"))); + assertTrue("b) Should contain entry", s.contains(new Attributes.Name( + "2"))); + assertTrue("c) Should contain entry", s.contains(new Attributes.Name( + "3"))); + assertTrue("d) Should contain entry", s.contains(new Attributes.Name( + "4"))); + } + + /** + * @tests java.util.jar.Attributes#putAll(java.util.Map) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "putAll", + args = {java.util.Map.class} + ) + public void test_putAllLjava_util_Map() { + Attributes b = new Attributes(); + b.putValue("3", "san"); + b.putValue("4", "shi"); + b.putValue("5", "go"); + b.putValue("6", "roku"); + a.putAll(b); + assertEquals("Should not have been replaced", "one", a.getValue("1")); + assertEquals("Should have been replaced", "san", a.getValue("3")); + assertEquals("Should have been added", "go", a.getValue("5")); + Attributes atts = new Attributes(); + assertNull("Assert 0: ", atts.put(Attributes.Name.CLASS_PATH, + "tools.jar")); + assertNull("Assert 1: ", atts + .put(Attributes.Name.MANIFEST_VERSION, "1")); + Attributes atts2 = new Attributes(); + atts2.putAll(atts); + assertEquals("Assert 2:", "tools.jar", atts2 + .get(Attributes.Name.CLASS_PATH)); + assertEquals("Assert 3: ", "1", atts2 + .get(Attributes.Name.MANIFEST_VERSION)); + try { + atts.putAll(Collections.EMPTY_MAP); + fail("Assert 4: no class cast from attrib parameter"); + } catch (ClassCastException e) { + // Expected + } + } + + /** + * @tests java.util.jar.Attributes#putAll(java.util.Map) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Regression test", + method = "putAll", + args = {java.util.Map.class} + ) + public void test_putAllLjava_util_Map2() { + // Regression for HARMONY-464 + try { + new Attributes().putAll((Map) null); + fail("ClassCastException expected"); + } catch (ClassCastException e) { + } + // verify that special care for null is done in the Attributes.putAll() + // method + try { + new Attributes() { + @Override + public void putAll(Map<?, ?> attrib) { + map.putAll(attrib); + } + }.putAll((Map<?, ?>) null); + fail("NullPointerException expected"); + } catch (NullPointerException e) { + } + } + + /** + * @tests java.util.jar.Attributes#remove(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "remove", + args = {java.lang.Object.class} + ) + public void test_removeLjava_lang_Object() { + a.remove(new Attributes.Name("1")); + a.remove(new Attributes.Name("3")); + assertNull("Should have been removed", a.getValue("1")); + assertEquals("Should not have been removed", "four", a.getValue("4")); + } + + /** + * @tests java.util.jar.Attributes#size() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "size", + args = {} + ) + public void test_size() { + assertEquals("Incorrect size returned", 4, a.size()); + a.clear(); + assertEquals(0, a.size()); + } + + /** + * @tests java.util.jar.Attributes#values() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "values", + args = {} + ) + public void test_values() { + Collection<?> valueCollection = a.values(); + assertTrue("a) Should contain entry", valueCollection.contains("one")); + assertTrue("b) Should contain entry", valueCollection.contains("two")); + assertTrue("c) Should contain entry", valueCollection.contains("three")); + assertTrue("d) Should contain entry", valueCollection.contains("four")); + } + + /** + * @tests java.util.jar.Attributes#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + Attributes a2 = (Attributes) a.clone(); + assertEquals(a, a2); + a.putValue("1", "one(1)"); + assertTrue("equal", !a.equals(a2)); + } + + /** + * @tests java.util.jar.Attributes#equals(java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equalsLjava_lang_Object() { + Attributes.Name n1 = new Attributes.Name("name"), n2 = new Attributes.Name( + "Name"); + assertEquals(n1, n2); + Attributes a1 = new Attributes(); + a1.putValue("one", "1"); + a1.putValue("two", "2"); + Attributes a2 = new Attributes(); + a2.putValue("One", "1"); + a2.putValue("TWO", "2"); + assertEquals(a1, a2); + } + + /** + * @tests java.util.jar.Attributes.put(java.lang.Object, java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Regression test. Checks ClassCastException", + method = "put", + args = {java.lang.Object.class, java.lang.Object.class} + ) + public void test_putLjava_lang_ObjectLjava_lang_Object() { + Attributes atts = new Attributes(); + assertNull("Assert 0: ", atts.put(Attributes.Name.CLASS_PATH, + "tools.jar")); + assertEquals("Assert 1: ", "tools.jar", atts + .getValue(Attributes.Name.CLASS_PATH)); + // Regression for HARMONY-79 + try { + atts.put("not a name", "value"); + fail("Assert 2: no class cast from key parameter"); + } catch (ClassCastException e) { + // Expected + } + try { + atts.put(Attributes.Name.CLASS_PATH, Boolean.TRUE); + fail("Assert 3: no class cast from value parameter"); + } catch (ClassCastException e) { + // Expected + } + } + + /** + * @tests java.util.jar.Attributes.put(java.lang.Object, java.lang.Object) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "ClassCastException checking missed.", + method = "put", + args = {java.lang.Object.class, java.lang.Object.class} + ) + public void test_putLjava_lang_ObjectLjava_lang_Object_Null() { + + Attributes attribute = new Attributes(); + + assertFalse(attribute.containsKey(null)); + assertFalse(attribute.containsValue(null)); + attribute.put(null, null); + attribute.put(null, null); + assertEquals(1, attribute.size()); + assertTrue(attribute.containsKey(null)); + assertTrue(attribute.containsValue(null)); + assertNull(attribute.get(null)); + + String value = "It's null"; + attribute.put(null, value); + assertEquals(1, attribute.size()); + assertEquals(value, attribute.get(null)); + + Attributes.Name name = new Attributes.Name("null"); + attribute.put(name, null); + assertEquals(2, attribute.size()); + assertNull(attribute.get(name)); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Attributes", + args = {} + ) + public void test_Constructor() { + Attributes attr = new Attributes(); + assertTrue(attr.size() >= 0); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Attributes", + args = {int.class} + ) + public void test_ConstructorI() { + Attributes attr = new Attributes(10); + assertTrue(attr.size() >= 0); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "get", + args = {java.lang.Object.class} + ) + public void test_getLjava_lang_Object_true() { + assertEquals("a) Incorrect value returned", "one", a + .get(new Attributes.Name("1"))); + assertNull("b) Incorrect value returned", a.get("0")); + assertNull("b) Incorrect value returned", a.get("1")); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getValue", + args = {java.util.jar.Attributes.Name.class} + ) + public void test_getValueLjava_util_jar_Attributes_Name() { + assertEquals("a) Incorrect value returned", "one", a + .getValue(new Attributes.Name("1"))); + assertNull("b) Incorrect value returned", a + .getValue(new Attributes.Name("0"))); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + Attributes b = (Attributes) a.clone(); + b.putValue("33", "Thirty three"); + assertNotSame(a.hashCode(), b.hashCode()); + b = (Attributes) a.clone(); + b.clear(); + assertNotSame(a.hashCode(), b.hashCode()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "putValue", + args = {java.lang.String.class, java.lang.String.class} + ) + public void test_putValueLjava_lang_StringLjava_lang_String() { + Attributes b = new Attributes(); + b.put(new Attributes.Name("1"), "one"); + b.putValue("2", "two"); + b.put(new Attributes.Name("3"), "three"); + b.putValue("4", "four"); + + assertTrue(a.equals(b)); + + try { + b.putValue(null, "null"); + fail("NullPointerException expected"); + } catch (NullPointerException ee) { + // expected + } + + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 0x10000; i++) { + sb.append('3'); + } + try { + b.putValue(new String(sb), "wrong name"); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException ee) { + // expected + } + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/DalvikExecTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/DalvikExecTest.java new file mode 100644 index 0000000..77fbb15 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/DalvikExecTest.java @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.AndroidOnly; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargets; + +import junit.framework.TestCase; + +import tests.support.Support_Exec; +import tests.support.resource.Support_Resources; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.jar.JarFile; + + +@TestTargetClass(JarOutputStream.class) +@AndroidOnly("dalvik vm specific") +public class DalvikExecTest extends TestCase { + + String execDalvik1 (String classpath, String mainClass, String arg1) + throws IOException, InterruptedException { + + ArrayList<String> cmdLine = new ArrayList<String>(10); + + String base = System.getenv("OUT"); + cmdLine.add(base + "/system/bin/dalvikvm"); + + cmdLine.add("-Djava.io.tmpdir=/tmp/mc"); + cmdLine.add("-Duser.language=en"); + cmdLine.add("-Duser.region=US"); + + if ("true".equals(System.getenv("TARGET_SIMULATOR"))) { + // Test against SIMULATOR: +// cmdLine.add("-Xmx512M"); +// cmdLine.add("-Xcheck:jni"); + cmdLine.add("-Xbootclasspath:" + System.getProperty("java.boot.class.path")); + } else { + // Test against EMULATOR: + } + + cmdLine.add("-classpath"); + cmdLine.add(classpath); + cmdLine.add(mainClass); + + if (arg1 != null) cmdLine.add(arg1); + + Object[] res = Support_Exec.execAndDigestOutput( + cmdLine.toArray(new String[cmdLine.size()]), + null ); + return Support_Exec.getProcessOutput(res, true); + } + + String execDalvik (String classpath, String mainClass) + throws IOException, InterruptedException { + return execDalvik1(classpath, mainClass, null); + } + + @TestTargets ({ + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Execute an existing JAR on dalvikvm using -classpath option.", + clazz = Runtime.class, + method = "exec", + args = {java.lang.String[].class} + ) + }) + public void test_execExistingJar () throws IOException, InterruptedException { + String res; + File jarFile; + if (System.getProperty("java.vendor").contains("Android")) { + // + // Test against Android: + // + File tempDir = Support_Resources.createTempFolder(); + jarFile = Support_Resources.copyFile( + tempDir, null, "cts_dalvikExecTest.jar" ); + res = execDalvik(jarFile.getAbsolutePath(), "dalvikExecTest.HelloWorld"); + assertEquals("Hello Android World!", "Hello Android World!\n", res); + + res = execDalvik(jarFile.getAbsolutePath(), "dalvikExecTest.ResourceDumper"); + assertTrue("Android Resource Dumper started", + res.contains("Android Resource Dumper started")); + assertTrue("This Resource contains some text.", + res.contains("This Resource contains some text.")); + } else { + // + // Test against RI: + // + // Do nothing! + } + } + + + @TestTargets ({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "putNextEntry", + args = {java.util.zip.ZipEntry.class} + ), + @TestTargetNew( + level = TestLevel.ADDITIONAL, + method = "JarOutputStream", + args = {java.io.OutputStream.class} + ), + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Create a temp file, fill it with contents according to Dalvik JAR format, and execute it on dalvikvm using -classpath option.", + clazz = Runtime.class, + method = "exec", + args = {java.lang.String[].class} + ) + }) + public void test_execCreatedJar () throws IOException, InterruptedException { + File jarFile = File.createTempFile("cts_dalvikExecTest_", ".jar"); + jarFile.deleteOnExit(); + + // Create a JAR output stream on the temp file: + JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(jarFile)); + + // Define the entry for the classes.dex: + jarOut.putNextEntry(new JarEntry("classes.dex")); + + // Fill in the classes.dex contents, i.e. the Dalvik executable code: + // (See below for the detailed source code contents.) + Support_Resources.writeResourceToStream("cts_dalvikExecTest_classes.dex", jarOut); + + // Now add a resource file: + // + jarOut.putNextEntry(new JarEntry("dalvikExecTest/myResource")); + jarOut.write("This Resource contains some text.".getBytes()); + + // Close the stream to the completed JAR file. + jarOut.close(); + + // The resulting JAR file contains the classes listed at the end of this text, + // like the 'cts_dalvikExecTest.jar' as part of the resources, too. + + String res; + + res = execDalvik(jarFile.getAbsolutePath(), "dalvikExecTest.HelloWorld"); + assertEquals("Hello Android World!", "Hello Android World!\n", res); + + res = execDalvik(jarFile.getAbsolutePath(), "dalvikExecTest.ResourceDumper"); + assertTrue("Android Resource Dumper started", + res.contains("Android Resource Dumper started")); + assertTrue("This Resource contains some text.", + res.contains("This Resource contains some text.")); + } + + + @TestTargets ({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "putNextEntry", + args = {java.util.zip.ZipEntry.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "JarOutputStream", + args = {java.io.OutputStream.class, java.util.jar.Manifest.class} + ), + @TestTargetNew( + level = TestLevel.ADDITIONAL, + clazz = Runtime.class, + method = "exec", + args = {java.lang.String[].class} + ) + }) + /** + * This test does quite the same as test_execCreatedJar, but includes a manifest. + * Note however that the Dalvik JAR format does not require this manifest. + * We just test whether the manifest is placed correctly within the JAR by + * dumping its contents read as a simple text resource. + * No! We can't do that so easily either, as there are other (parent) JARs + * with a manifest inside, taken with precedence. + * So we will reopen the JAR as a JarFile and check the manifest + * with a top level end-to-end approach. + */ + public void test_execCreatedJarWithManifest () throws IOException, InterruptedException { + File jarFile = File.createTempFile("cts_dalvikExecTest_", ".jar"); + jarFile.deleteOnExit(); + + // Create the manifest: + Manifest manifest = new Manifest(); + Attributes attrs = manifest.getMainAttributes(); + attrs.put(Attributes.Name.MANIFEST_VERSION, "3.1415962"); + attrs.put(Attributes.Name.MAIN_CLASS, "dalvikExecTest.HelloWorld"); + attrs.put(Attributes.Name.CLASS_PATH, jarFile.getName()); + + // Create a JAR output stream on the temp file using the manifest: + JarOutputStream jarOut = new JarOutputStream(new FileOutputStream(jarFile), manifest); + + // Define the entry for the classes.dex: + jarOut.putNextEntry(new JarEntry("classes.dex")); + + // Fill in the classes.dex contents, i.e. the Dalvik executable code: + // (See below for the detailed source code contents.) + Support_Resources.writeResourceToStream("cts_dalvikExecTest_classes.dex", jarOut); + + // Now add a resource file: + // + jarOut.putNextEntry(new JarEntry("dalvikExecTest/myResource")); + jarOut.write("This Resource contains some text.".getBytes()); + + // Close the stream to the completed JAR file. + jarOut.close(); + + // The resulting JAR file contains the classes listed at the end of this text, + // like the 'cts_dalvikExecTest.jar' as part of the resources, too. + + String res; + + res = execDalvik(jarFile.getAbsolutePath(), "dalvikExecTest.HelloWorld"); + assertEquals("Hello Android World!", "Hello Android World!\n", res); + + res = execDalvik(jarFile.getAbsolutePath(), "dalvikExecTest.ResourceDumper"); + assertTrue("Android Resource Dumper started", + res.contains("Android Resource Dumper started")); + assertTrue("This Resource contains some text.", + res.contains("This Resource contains some text.")); + + // And now reread the manifest: + // + JarFile jarIn = new JarFile(jarFile); + manifest = jarIn.getManifest(); + attrs = manifest.getMainAttributes(); + assertEquals("MANIFEST_VERSION must match!", "3.1415962", + attrs.get(Attributes.Name.MANIFEST_VERSION)); + assertEquals("MAIN_CLASS must match!", "dalvikExecTest.HelloWorld", + attrs.get(Attributes.Name.MAIN_CLASS)); + assertEquals("CLASS_PATH must match!", jarFile.getName(), + attrs.get(Attributes.Name.CLASS_PATH)); + } + + + /* + * The following two classes are added, here, only for completeness. + * They form the contents of the dalvikExecTest package contained + * in the 'cts_dalvikExecTest_classes.dex' resource file. + */ + /** + * @hide + */ + public static class HelloWorld { + + public static void main(String[] args) { + System.out.println("Hello Android World!"); + } + + } + + public static class ResourceDumper { + + static ByteArrayOutputStream outputFrom (InputStream input) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[512]; + int total = 0; + int count; + count = input.read(buffer); + while (count != -1) { + out.write(buffer, 0, count); + total = total + count; + count = input.read(buffer); + } + return out; + } + + public static void main(String[] args) throws IOException { + System.out.print("Android Resource Dumper started "); + String fileName; + if (args.length >= 1) { + fileName = args[0]; + System.out.format("for argument '%s'.\n", fileName); + } else { + System.out.print("standard "); + fileName = "myResource"; + System.out.println("for standard 'myResource'."); + } + InputStream is = ResourceDumper.class.getResourceAsStream(fileName); + if (is != null) { + System.out.println("Resource obtained and being dumped:"); + System.out.println(outputFrom(is).toString()); + } + } + + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarEntryTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarEntryTest.java new file mode 100644 index 0000000..40eff3b --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarEntryTest.java @@ -0,0 +1,259 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.security.CodeSigner; +import java.util.Enumeration; +import java.util.List; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.zip.ZipEntry; +import junit.framework.TestCase; +import tests.support.resource.Support_Resources; + + +@TestTargetClass(JarEntry.class) +public class JarEntryTest extends TestCase { + private ZipEntry zipEntry; + + private JarEntry jarEntry; + + private JarFile jarFile; + + private final String jarName = "hyts_patch.jar"; + + private final String entryName = "foo/bar/A.class"; + + private final String entryName2 = "Blah.txt"; + + private final String attJarName = "hyts_att.jar"; + + private final String attEntryName = "HasAttributes.txt"; + + private final String attEntryName2 = "NoAttributes.txt"; + + private File resources; + + @Override + protected void setUp() throws Exception { + resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, jarName); + jarFile = new JarFile(new File(resources, jarName)); + } + + @Override + protected void tearDown() throws Exception { + if (jarFile != null) { + jarFile.close(); + } + } + + /** + * @tests java.util.jar.JarEntry#JarEntry(java.util.zip.ZipEntry) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarEntry", + args = {java.util.zip.ZipEntry.class} + ) + public void test_ConstructorLjava_util_zip_ZipEntry() { + assertNotNull("Jar file is null", jarFile); + zipEntry = jarFile.getEntry(entryName); + assertNotNull("Zip entry is null", zipEntry); + jarEntry = new JarEntry(zipEntry); + assertNotNull("Jar entry is null", jarEntry); + assertEquals("Wrong entry constructed--wrong name", entryName, jarEntry + .getName()); + assertEquals("Wrong entry constructed--wrong size", 311, jarEntry + .getSize()); + } + + /** + * @tests java.util.jar.JarEntry#getAttributes() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAttributes", + args = {} + ) + public void test_getAttributes() { + JarFile attrJar = null; + File file = null; + try { + Support_Resources.copyFile(resources, null, attJarName); + file = new File(resources, attJarName); + attrJar = new JarFile(file); + } catch (Exception e) { + assertTrue(file + " does not exist", file.exists()); + fail("Exception opening file: " + e.toString()); + } + try { + jarEntry = attrJar.getJarEntry(attEntryName); + assertNotNull("Should have Manifest attributes", jarEntry + .getAttributes()); + } catch (Exception e) { + fail("Exception during 2nd test: " + e.toString()); + } + try { + jarEntry = attrJar.getJarEntry(attEntryName2); + assertNull("Shouldn't have any Manifest attributes", jarEntry + .getAttributes()); + attrJar.close(); + } catch (Exception e) { + fail("Exception during 1st test: " + e.toString()); + } + + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + try { + attrJar = new JarFile(new File(resources, "Broken_manifest.jar")); + jarEntry = attrJar.getJarEntry("META-INF/"); + jarEntry.getAttributes(); + fail("IOException expected"); + } catch (IOException e) { + // expected. + } + } + + /** + * @tests java.util.jar.JarEntry#getCertificates() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCertificates", + args = {} + ) + public void test_getCertificates() throws Exception { + zipEntry = jarFile.getEntry(entryName2); + jarEntry = new JarEntry(zipEntry); + assertNull("Shouldn't have any Certificates", jarEntry + .getCertificates()); + + // Regression Test for HARMONY-3424 + String jarFileName = "TestCodeSigners.jar"; + Support_Resources.copyFile(resources, null, jarFileName); + File file = new File(resources, jarFileName); + JarFile jarFile = new JarFile(file); + JarEntry jarEntry1 = jarFile.getJarEntry("Test.class"); + JarEntry jarEntry2 = jarFile.getJarEntry("Test.class"); + InputStream in = jarFile.getInputStream(jarEntry1); + byte[] buffer = new byte[1024]; + while (in.read(buffer) >= 0); + in.close(); + assertNotNull(jarEntry1.getCertificates()); + assertNotNull(jarEntry2.getCertificates()); + } + + /** + * @tests java.util.jar.JarEntry#getCodeSigners() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCodeSigners", + args = {} + ) + public void test_getCodeSigners() throws IOException { + String jarFileName = "TestCodeSigners.jar"; + Support_Resources.copyFile(resources, null, jarFileName); + File file = new File(resources, jarFileName); + JarFile jarFile = new JarFile(file); + JarEntry jarEntry = jarFile.getJarEntry("Test.class"); + InputStream in = jarFile.getInputStream(jarEntry); + byte[] buffer = new byte[1024]; + while (in.available() > 0) { + in.read(buffer); + } + CodeSigner[] codeSigners = jarEntry.getCodeSigners(); + assertEquals(2, codeSigners.length); + List<?> certs_bob = codeSigners[0].getSignerCertPath() + .getCertificates(); + List<?> certs_alice = codeSigners[1].getSignerCertPath() + .getCertificates(); + if (1 == certs_bob.size()) { + List<?> temp = certs_bob; + certs_bob = certs_alice; + certs_alice = temp; + } + assertEquals(2, certs_bob.size()); + assertEquals(1, certs_alice.size()); + assertNull( + "getCodeSigners() of a primitive JarEntry should return null", + new JarEntry("aaa").getCodeSigners()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarEntry", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + assertNotNull("Jar file is null", jarFile); + zipEntry = jarFile.getEntry(entryName); + assertNotNull("Zip entry is null", zipEntry); + jarEntry = new JarEntry(entryName); + assertNotNull("Jar entry is null", jarEntry); + assertEquals("Wrong entry constructed--wrong name", entryName, jarEntry + .getName()); + try { + jarEntry = new JarEntry((String) null); + fail("NullPointerException expected"); + } catch (NullPointerException ee) { + // expected + } + StringBuffer sb = new StringBuffer(); + for (int i = 0; i < 0x10000; i++) { + sb.append('3'); + } + try { + jarEntry = new JarEntry(new String(sb)); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException ee) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarEntry", + args = {java.util.jar.JarEntry.class} + ) + public void test_ConstructorLjava_util_jar_JarEntry() { + assertNotNull("Jar file is null", jarFile); + JarEntry je = jarFile.getJarEntry(entryName); + assertNotNull("Jar entry is null", je); + jarEntry = new JarEntry(je); + assertNotNull("Jar entry is null", jarEntry); + assertEquals("Wrong entry constructed--wrong name", entryName, jarEntry + .getName()); + assertEquals("Wrong entry constructed--wrong size", 311, jarEntry + .getSize()); + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExceptionTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExceptionTest.java new file mode 100644 index 0000000..7f48342 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExceptionTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.jar.Manifest; +import junit.framework.TestCase; +import java.util.jar.JarException; + +@TestTargetClass(JarException.class) +public class JarExceptionTest extends TestCase { + /** + * @tests java.util.jar.JarException#JarException(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarException", + args = {} + ) + public void test_Constructor() throws Exception { + JarException ex = new JarException(); + JarException ex1 = new JarException("Test string"); + JarException ex2 = new JarException(null); + assertNotSame(ex, ex1); + assertNotSame(ex.getMessage(), ex1.getMessage()); + assertNotSame(ex, ex2); + assertSame(ex.getMessage(), ex2.getMessage()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarException", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() throws Exception { + JarException ex1 = new JarException("Test string"); + JarException ex2 = new JarException(null); + assertNotSame(ex1, ex2); + assertNotSame(ex1.getMessage(), ex2.getMessage()); + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExecTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExecTest.java new file mode 100644 index 0000000..4c209d1 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExecTest.java @@ -0,0 +1,317 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import tests.support.Support_Exec; +import tests.support.resource.Support_Resources; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; + +/** + * + * tests for various cases of java -jar ... execution + * + */ + +@TestTargetClass(JarOutputStream.class) +public class JarExecTest extends junit.framework.TestCase { + /** + * regression test for HARMONY-1562 issue + * + */ + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Regression functional test. Exception checking missed.", + method = "putNextEntry", + args = {java.util.zip.ZipEntry.class} + ) + @KnownFailure("Maybe not a failure, but dalvikvm -jar is not supported (, as yet).") + public void test_1562() throws Exception { + // create the manifest + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + + File outputJar = File.createTempFile("hyts_", ".jar"); + outputJar.deleteOnExit(); + JarOutputStream jout = new JarOutputStream(new FileOutputStream( + outputJar), man); + File resources = Support_Resources.createTempFolder(); + + for (String jarClass : new String[] {"Foo", "Bar"}) { + jout.putNextEntry(new JarEntry("foo/bar/execjartest/" + jarClass + + ".class")); + jout.write(getResource(resources, "hyts_" + jarClass + ".ser")); + } + + jout.close(); + + + // set up the VM parameters + String[] args = new String[] {"-jar", outputJar.getAbsolutePath()}; + + // execute the JAR and read the result + String res = Support_Exec.execJava(args, null, false); + + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + } + + /** + * tests Class-Path entry in manifest + * + * @throws Exception in case of troubles + */ + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Functional test.", + method = "JarOutputStream", + args = {java.io.OutputStream.class, java.util.jar.Manifest.class} + ) + @KnownFailure("Maybe not a failure, but dalvikvm -jar is not supported (, as yet).") + public void test_jar_class_path() throws Exception { + File fooJar = File.createTempFile("hyts_", ".jar"); + File barJar = File.createTempFile("hyts_", ".jar"); + fooJar.deleteOnExit(); + barJar.deleteOnExit(); + + // create the manifest + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + att.put(Attributes.Name.CLASS_PATH, barJar.getName()); + + File resources = Support_Resources.createTempFolder(); + + JarOutputStream joutFoo = new JarOutputStream(new FileOutputStream( + fooJar), man); + joutFoo.putNextEntry(new JarEntry("foo/bar/execjartest/Foo.class")); + joutFoo.write(getResource(resources, "hyts_Foo.ser")); + joutFoo.close(); + + JarOutputStream joutBar = new JarOutputStream(new FileOutputStream( + barJar)); + joutBar.putNextEntry(new JarEntry("foo/bar/execjartest/Bar.class")); + joutBar.write(getResource(resources, "hyts_Bar.ser")); + joutBar.close(); + + String[] args = new String[] {"-jar", fooJar.getAbsolutePath()}; + + // execute the JAR and read the result + String res = Support_Exec.execJava(args, null, false); + + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + + // rewrite manifest so it contains not only reference to bar but useless + // entries as well + att.put(Attributes.Name.CLASS_PATH, "xx yy zz " + barJar.getName()); + joutFoo = new JarOutputStream(new FileOutputStream(fooJar), man); + joutFoo.putNextEntry(new JarEntry("foo/bar/execjartest/Foo.class")); + joutFoo.write(getResource(resources, "hyts_Foo.ser")); + joutFoo.close(); + // execute the JAR and read the result + res = Support_Exec.execJava(args, null, false); + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + + + // play with relative file names - put relative path as ../<parent dir + // name>/xx.jar + att.put(Attributes.Name.CLASS_PATH, ".." + File.separator + + barJar.getParentFile().getName() + File.separator + + barJar.getName()); + joutFoo = new JarOutputStream(new FileOutputStream(fooJar), man); + joutFoo.putNextEntry(new JarEntry("foo/bar/execjartest/Foo.class")); + joutFoo.write(getResource(resources, "hyts_Foo.ser")); + joutFoo.close(); + // execute the JAR and read the result + res = Support_Exec.execJava(args, null, false); + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + } + + /** + * tests case when Main-Class is not in the jar launched but in another jar + * referenced by Class-Path + * + * @throws Exception in case of troubles + */ + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Functional test.", + method = "JarOutputStream", + args = {java.io.OutputStream.class, java.util.jar.Manifest.class} + ) + @KnownFailure("Maybe not a failure, but dalvikvm -jar is not supported (, as yet).") + public void test_main_class_in_another_jar() throws Exception { + File fooJar = File.createTempFile("hyts_", ".jar"); + File barJar = File.createTempFile("hyts_", ".jar"); + fooJar.deleteOnExit(); + barJar.deleteOnExit(); + + // create the manifest + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + att.put(Attributes.Name.CLASS_PATH, fooJar.getName()); + + File resources = Support_Resources.createTempFolder(); + + JarOutputStream joutFoo = new JarOutputStream(new FileOutputStream( + fooJar)); + joutFoo.putNextEntry(new JarEntry("foo/bar/execjartest/Foo.class")); + joutFoo.write(getResource(resources, "hyts_Foo.ser")); + joutFoo.close(); + + JarOutputStream joutBar = new JarOutputStream(new FileOutputStream( + barJar), man); + joutBar.putNextEntry(new JarEntry("foo/bar/execjartest/Bar.class")); + joutBar.write(getResource(resources, "hyts_Bar.ser")); + joutBar.close(); + + String[] args = new String[] {"-jar", barJar.getAbsolutePath()}; + + // execute the JAR and read the result + String res = Support_Exec.execJava(args, null, false); + + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + } + + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Functional test.", + method = "JarOutputStream", + args = {java.io.OutputStream.class, java.util.jar.Manifest.class} + ) + @KnownFailure("Maybe not a failure, but dalvikvm -jar is not supported (, as yet).") + public void test_classpath() throws Exception { + File resources = Support_Resources.createTempFolder(); + + File fooJar = File.createTempFile("hyts_", ".jar"); + fooJar.deleteOnExit(); + + JarOutputStream joutFoo = new JarOutputStream(new FileOutputStream( + fooJar)); + joutFoo.putNextEntry(new JarEntry("foo/bar/execjartest/Foo.class")); + joutFoo.write(getResource(resources, "hyts_Foo.ser")); + joutFoo.putNextEntry(new JarEntry("foo/bar/execjartest/Bar.class")); + joutFoo.write(getResource(resources, "hyts_Bar.ser")); + joutFoo.close(); + + String[] args = new String[] {"foo.bar.execjartest.Foo"}; + + // execute the JAR and read the result + String res = Support_Exec.execJava(args, null, + new String[] {"CLASSPATH=" + fooJar.getAbsolutePath()}, false); + + assertTrue( + "Error executing class from ClassPath : result returned was incorrect.", + res.startsWith("FOOBAR")); + + // ok - next try - add -cp to path - it should override env + File booJar = File.createTempFile("hyts_", ".jar"); + booJar.deleteOnExit(); + + JarOutputStream joutBoo = new JarOutputStream(new FileOutputStream( + booJar)); + joutBoo.putNextEntry(new JarEntry("foo/bar/execjartest/Foo.class")); + String booBody = new String(getResource(resources, "hyts_Foo.ser"), + "iso-8859-1"); + booBody = booBody.replaceFirst("FOO", "BOO"); + joutBoo.write(booBody.getBytes("iso-8859-1")); + joutBoo.putNextEntry(new JarEntry("foo/bar/execjartest/Bar.class")); + String farBody = new String(getResource(resources, "hyts_Bar.ser"), + "iso-8859-1"); + farBody = farBody.replaceFirst("BAR", "FAR"); + joutBoo.write(farBody.getBytes("iso-8859-1")); + joutBoo.close(); + + res = Support_Exec.execJava(args, new String[] {booJar + .getAbsolutePath()}, new String[] {"CLASSPATH=" + + fooJar.getAbsolutePath()}, false); + + assertTrue( + "Error executing class specified by -cp : result returned was incorrect.", + res.startsWith("BOOFAR")); + + // now add -jar option - it should override env and classpath + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + + File zooJar = File.createTempFile("hyts_", ".jar"); + zooJar.deleteOnExit(); + + JarOutputStream joutZoo = new JarOutputStream(new FileOutputStream( + zooJar), man); + joutZoo.putNextEntry(new JarEntry("foo/bar/execjartest/Foo.class")); + String zooBody = new String(getResource(resources, "hyts_Foo.ser"), + "iso-8859-1"); + zooBody = zooBody.replaceFirst("FOO", "ZOO"); + joutZoo.write(zooBody.getBytes("iso-8859-1")); + joutZoo.putNextEntry(new JarEntry("foo/bar/execjartest/Bar.class")); + String zarBody = new String(getResource(resources, "hyts_Bar.ser"), + "iso-8859-1"); + zarBody = zarBody.replaceFirst("BAR", "ZAR"); + joutZoo.write(zarBody.getBytes("iso-8859-1")); + joutZoo.close(); + + args = new String[] {"-jar", zooJar.getAbsolutePath()}; + + res = Support_Exec.execJava(args, new String[] {booJar + .getAbsolutePath()}, new String[] {"CLASSPATH=" + + fooJar.getAbsolutePath()}, false); + + assertTrue( + "Error executing class specified by -cp : result returned was incorrect.", + res.startsWith("ZOOZAR")); + } + + private static byte[] getResource(File tempDir, String resourceName) + throws IOException { + Support_Resources.copyFile(tempDir, null, resourceName); + File resourceFile = new File(tempDir, resourceName); + resourceFile.deleteOnExit(); + + // read whole resource data into memory + byte[] resourceBody = new byte[(int) resourceFile.length()]; + FileInputStream fis = new FileInputStream(resourceFile); + fis.read(resourceBody); + fis.close(); + + return resourceBody; + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarFileTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarFileTest.java new file mode 100644 index 0000000..497b897 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarFileTest.java @@ -0,0 +1,929 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + + +import dalvik.annotation.AndroidOnly; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.security.Permission; +import java.security.cert.Certificate; +import java.util.Enumeration; +import java.util.Vector; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +import junit.framework.TestCase; +import tests.support.Support_PlatformFile; +import tests.support.resource.Support_Resources; + + +@TestTargetClass(JarFile.class) +public class JarFileTest extends TestCase { + + // BEGIN android-added + public byte[] getAllBytesFromStream(InputStream is) throws IOException { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + byte[] buf = new byte[666]; + int iRead; + int off; + while (is.available() > 0) { + iRead = is.read(buf, 0, buf.length); + if (iRead > 0) bs.write(buf, 0, iRead); + } + return bs.toByteArray(); + } + + // END android-added + + private final String jarName = "hyts_patch.jar"; // a 'normal' jar file + + private final String jarName2 = "hyts_patch2.jar"; + + private final String jarName3 = "hyts_manifest1.jar"; + + private final String jarName4 = "hyts_signed.jar"; + + private final String jarName5 = "hyts_signed_inc.jar"; + + private final String entryName = "foo/bar/A.class"; + + private final String entryName3 = "coucou/FileAccess.class"; + + private File resources; + + // custom security manager + SecurityManager sm = new SecurityManager() { + final String forbidenPermissionName = "user.dir"; + + public void checkPermission(Permission perm) { + if (perm.getName().equals(forbidenPermissionName)) { + throw new SecurityException(); + } + } + }; + + @Override + protected void setUp() { + resources = Support_Resources.createTempFolder(); + } + + /** + * @tests java.util.jar.JarFile#JarFile(java.io.File) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarFile", + args = {java.io.File.class} + ) + public void test_ConstructorLjava_io_File() { + try { + JarFile jarFile = new JarFile(new File("Wrong.file")); + fail("Should throw IOException"); + } catch (IOException e) { + // expected + } + SecurityManager oldSm = System.getSecurityManager(); + System.setSecurityManager(sm); + try { + JarFile jarFile = new JarFile(new File("tmp.jar")); + fail("Should throw SecurityException"); + } catch (IOException e) { + fail("Should throw SecurityException"); + } catch (SecurityException e) { + // expected + } finally { + System.setSecurityManager(oldSm); + } + + try { + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + } catch (IOException e) { + fail("Should not throw IOException"); + } + } + + /** + * @tests java.util.jar.JarFile#JarFile(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarFile", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + try { + JarFile jarFile = new JarFile("Wrong.file"); + fail("Should throw IOException"); + } catch (IOException e) { + // expected + } + SecurityManager oldSm = System.getSecurityManager(); + System.setSecurityManager(sm); + try { + JarFile jarFile = new JarFile("tmp.jar"); + fail("Should throw SecurityException"); + } catch (IOException e) { + fail("Should throw SecurityException"); + } catch (SecurityException e) { + // expected + } finally { + System.setSecurityManager(oldSm); + } + + try { + Support_Resources.copyFile(resources, null, jarName); + String fileName = (new File(resources, jarName)).getCanonicalPath(); + JarFile jarFile = new JarFile(fileName); + } catch (IOException e) { + fail("Should not throw IOException"); + } + } + + /** + * @tests java.util.jar.JarFile#JarFile(java.lang.String, boolean) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarFile", + args = {java.lang.String.class, boolean.class} + ) + public void test_ConstructorLjava_lang_StringZ() { + try { + JarFile jarFile = new JarFile("Wrong.file", false); + fail("Should throw IOException"); + } catch (IOException e) { + // expected + } + SecurityManager oldSm = System.getSecurityManager(); + System.setSecurityManager(sm); + try { + JarFile jarFile = new JarFile("tmp.jar", true); + fail("Should throw SecurityException"); + } catch (IOException e) { + fail("Should throw SecurityException"); + } catch (SecurityException e) { + // expected + } finally { + System.setSecurityManager(oldSm); + } + + try { + Support_Resources.copyFile(resources, null, jarName); + String fileName = (new File(resources, jarName)).getCanonicalPath(); + JarFile jarFile = new JarFile(fileName, true); + } catch (IOException e) { + fail("Should not throw IOException"); + } + } + + /** + * @tests java.util.jar.JarFile#JarFile(java.io.File, boolean) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarFile", + args = {java.io.File.class, boolean.class} + ) + public void test_ConstructorLjava_io_FileZ() { + try { + JarFile jarFile = new JarFile(new File("Wrong.file"), true); + fail("Should throw IOException"); + } catch (IOException e) { + // expected + } + SecurityManager oldSm = System.getSecurityManager(); + System.setSecurityManager(sm); + try { + JarFile jarFile = new JarFile(new File("tmp.jar"), false); + fail("Should throw SecurityException"); + } catch (IOException e) { + fail("Should throw SecurityException"); + } catch (SecurityException e) { + // expected + } finally { + System.setSecurityManager(oldSm); + } + + try { + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName), false); + } catch (IOException e) { + fail("Should not throw IOException"); + } + } + + /** + * @tests java.util.jar.JarFile#JarFile(java.io.File, boolean, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarFile", + args = {java.io.File.class, boolean.class, int.class} + ) + public void test_ConstructorLjava_io_FileZI() { + try { + JarFile jarFile = new JarFile(new File("Wrong.file"), true, + ZipFile.OPEN_READ); + fail("Should throw IOException"); + } catch (IOException e) { + // expected + } + SecurityManager oldSm = System.getSecurityManager(); + System.setSecurityManager(sm); + try { + JarFile jarFile = new JarFile(new File("tmp.jar"), false, + ZipFile.OPEN_READ); + fail("Should throw SecurityException"); + } catch (IOException e) { + fail("Should throw SecurityException"); + } catch (SecurityException e) { + // expected + } finally { + System.setSecurityManager(oldSm); + } + + try { + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName), false, + ZipFile.OPEN_READ); + } catch (IOException e) { + fail("Should not throw IOException"); + } + + try { + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName), false, + ZipFile.OPEN_READ | ZipFile.OPEN_DELETE + 33); + fail("Should throw IllegalArgumentException"); + } catch (IOException e) { + fail("Should not throw IOException"); + } catch (IllegalArgumentException e) { + // expected + } + } + + /** + * @tests java.util.jar.JarFile#entries() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "entries", + args = {} + ) + public void test_entries() throws Exception { + /* + * Note only (and all of) the following should be contained in the file + * META-INF/ META-INF/MANIFEST.MF foo/ foo/bar/ foo/bar/A.class Blah.txt + */ + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + Enumeration<JarEntry> e = jarFile.entries(); + int i = 0; + while (e.hasMoreElements()) { + i++; + e.nextElement(); + } + jarFile.close(); + assertEquals(6, i); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "entries", + args = {} + ) + public void test_entries2() throws Exception { + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + Enumeration<JarEntry> enumeration = jarFile.entries(); + jarFile.close(); + boolean pass = false; + try { + enumeration.hasMoreElements(); + } catch (IllegalStateException e) { + pass = true; + } + assertTrue("hasMoreElements did not detect closed jar file", pass); + Support_Resources.copyFile(resources, null, jarName); + jarFile = new JarFile(new File(resources, jarName)); + enumeration = jarFile.entries(); + jarFile.close(); + pass = false; + try { + enumeration.nextElement(); + } catch (IllegalStateException e) { + pass = true; + } + assertTrue("nextElement did not detect closed jar file", pass); + } + + /** + * @throws IOException + * @tests java.util.jar.JarFile#getJarEntry(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getEntry", + args = {java.lang.String.class} + ) + public void test_getEntryLjava_lang_String() throws IOException { + try { + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + assertEquals("Error in returned entry", 311, jarFile.getEntry( + entryName).getSize()); + jarFile.close(); + } catch (Exception e) { + fail("Exception during test: " + e.toString()); + } + + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + Enumeration<JarEntry> enumeration = jarFile.entries(); + assertTrue(enumeration.hasMoreElements()); + while (enumeration.hasMoreElements()) { + JarEntry je = enumeration.nextElement(); + jarFile.getEntry(je.getName()); + } + + enumeration = jarFile.entries(); + assertTrue(enumeration.hasMoreElements()); + JarEntry je = enumeration.nextElement(); + try { + jarFile.close(); + jarFile.getEntry(je.getName()); + // fail("IllegalStateException expected."); + } catch (IllegalStateException ee) { // Per documentation exception + // may be thrown. + // expected + } + } + + /** + * @throws IOException + * @tests java.util.jar.JarFile#getJarEntry(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getJarEntry", + args = {java.lang.String.class} + ) + public void test_getJarEntryLjava_lang_String() throws IOException { + try { + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + assertEquals("Error in returned entry", 311, jarFile.getJarEntry( + entryName).getSize()); + jarFile.close(); + } catch (Exception e) { + fail("Exception during test: " + e.toString()); + } + + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + Enumeration<JarEntry> enumeration = jarFile.entries(); + assertTrue(enumeration.hasMoreElements()); + while (enumeration.hasMoreElements()) { + JarEntry je = enumeration.nextElement(); + jarFile.getJarEntry(je.getName()); + } + + enumeration = jarFile.entries(); + assertTrue(enumeration.hasMoreElements()); + JarEntry je = enumeration.nextElement(); + try { + jarFile.close(); + jarFile.getJarEntry(je.getName()); + // fail("IllegalStateException expected."); + } catch (IllegalStateException ee) { // Per documentation exception + // may be thrown. + // expected + } + } + + /** + * @tests java.util.jar.JarFile#getManifest() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getManifest", + args = {} + ) + public void test_getManifest() { + // Test for method java.util.jar.Manifest + // java.util.jar.JarFile.getManifest() + try { + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + assertNotNull("Error--Manifest not returned", jarFile.getManifest()); + jarFile.close(); + } catch (Exception e) { + fail("Exception during 1st test: " + e.toString()); + } + try { + Support_Resources.copyFile(resources, null, jarName2); + JarFile jarFile = new JarFile(new File(resources, jarName2)); + assertNull("Error--should have returned null", jarFile + .getManifest()); + jarFile.close(); + } catch (Exception e) { + fail("Exception during 2nd test: " + e.toString()); + } + + try { + // jarName3 was created using the following test + Support_Resources.copyFile(resources, null, jarName3); + JarFile jarFile = new JarFile(new File(resources, jarName3)); + assertNotNull("Should find manifest without verifying", jarFile + .getManifest()); + jarFile.close(); + } catch (Exception e) { + fail("Exception during 3rd test: " + e.toString()); + } + + try { + // this is used to create jarName3 used in the previous test + Manifest manifest = new Manifest(); + Attributes attributes = manifest.getMainAttributes(); + attributes.put(new Attributes.Name("Manifest-Version"), "1.0"); + ByteArrayOutputStream manOut = new ByteArrayOutputStream(); + manifest.write(manOut); + byte[] manBytes = manOut.toByteArray(); + File file = new File(Support_PlatformFile.getNewPlatformFile( + "hyts_manifest1", ".jar")); + JarOutputStream jarOut = new JarOutputStream(new FileOutputStream( + file.getAbsolutePath())); + ZipEntry entry = new ZipEntry("META-INF/"); + entry.setSize(0); + jarOut.putNextEntry(entry); + entry = new ZipEntry(JarFile.MANIFEST_NAME); + entry.setSize(manBytes.length); + jarOut.putNextEntry(entry); + jarOut.write(manBytes); + entry = new ZipEntry("myfile"); + entry.setSize(1); + jarOut.putNextEntry(entry); + jarOut.write(65); + jarOut.close(); + JarFile jar = new JarFile(file.getAbsolutePath(), false); + assertNotNull("Should find manifest without verifying", jar + .getManifest()); + jar.close(); + file.delete(); + } catch (IOException e) { + fail("IOException 3"); + } + try { + Support_Resources.copyFile(resources, null, jarName2); + JarFile jF = new JarFile(new File(resources, jarName2)); + jF.close(); + jF.getManifest(); + fail("FAILED: expected IllegalStateException"); + } catch (IllegalStateException ise) { + // expected; + } catch (Exception e) { + fail("Exception during 4th test: " + e.toString()); + } + + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + JarFile jf; + try { + jf = new JarFile(new File(resources, "Broken_manifest.jar")); + jf.getManifest(); + fail("IOException expected."); + } catch (IOException e) { + // expected. + } + } + + /** + * @throws IOException + * @tests java.util.jar.JarFile#getInputStream(java.util.zip.ZipEntry) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInputStream", + args = {java.util.zip.ZipEntry.class} + ) + public void test_getInputStreamLjava_util_jar_JarEntry() throws IOException { + File localFile = null; + try { + Support_Resources.copyFile(resources, null, jarName); + localFile = new File(resources, jarName); + } catch (Exception e) { + fail("Failed to create local file: " + e); + } + + byte[] b = new byte[1024]; + try { + JarFile jf = new JarFile(localFile); + java.io.InputStream is = jf.getInputStream(jf.getEntry(entryName)); + // BEGIN android-removed + // jf.close(); + // END android-removed + assertTrue("Returned invalid stream", is.available() > 0); + int r = is.read(b, 0, 1024); + is.close(); + StringBuffer sb = new StringBuffer(r); + for (int i = 0; i < r; i++) { + sb.append((char) (b[i] & 0xff)); + } + String contents = sb.toString(); + assertTrue("Incorrect stream read", contents.indexOf("bar") > 0); + // BEGIN android-added + jf.close(); + // END android-added + } catch (Exception e) { + fail("Exception during test: " + e.toString()); + } + + try { + JarFile jf = new JarFile(localFile); + InputStream in = jf.getInputStream(new JarEntry("invalid")); + assertNull("Got stream for non-existent entry", in); + } catch (Exception e) { + fail("Exception during test 2: " + e); + } + + try { + Support_Resources.copyFile(resources, null, jarName); + File signedFile = new File(resources, jarName); + JarFile jf = new JarFile(signedFile); + JarEntry jre = new JarEntry("foo/bar/A.class"); + jf.getInputStream(jre); + // InputStream returned in any way, exception can be thrown in case + // of reading from this stream only. + // fail("Should throw ZipException"); + } catch (ZipException ee) { + // expected + } + + try { + Support_Resources.copyFile(resources, null, jarName); + File signedFile = new File(resources, jarName); + JarFile jf = new JarFile(signedFile); + JarEntry jre = new JarEntry("foo/bar/A.class"); + jf.close(); + jf.getInputStream(jre); + // InputStream returned in any way, exception can be thrown in case + // of reading from this stream only. + // The same for IOException + fail("Should throw IllegalStateException"); + } catch (IllegalStateException ee) { + // expected + } + } + + /** + * @tests java.util.jar.JarFile#getInputStream(java.util.zip.ZipEntry) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "SecurityException and functionality checked.", + method = "getInputStream", + args = {java.util.zip.ZipEntry.class} + ) + @AndroidOnly("This test doesn't pass on RI. If entry size is set up " + + "incorrectly, SecurityException is thrown. " + + "But SecurityException is thrown on RI only " + + "if jar file is signed incorreclty.") + public void test_getInputStreamLjava_util_jar_JarEntry_subtest0() { + File signedFile = null; + try { + Support_Resources.copyFile(resources, null, jarName4); + signedFile = new File(resources, jarName4); + } catch (Exception e) { + fail("Failed to create local file 2: " + e); + } + + try { + JarFile jar = new JarFile(signedFile); + JarEntry entry = new JarEntry(entryName3); + InputStream in = jar.getInputStream(entry); + in.read(); + } catch (Exception e) { + fail("Exception during test 3: " + e); + } + + try { + JarFile jar = new JarFile(signedFile); + JarEntry entry = new JarEntry(entryName3); + InputStream in = jar.getInputStream(entry); + // BEGIN android-added + byte[] dummy = getAllBytesFromStream(in); + // END android-added + assertNull("found certificates", entry.getCertificates()); + } catch (Exception e) { + fail("Exception during test 4: " + e); + } + + try { + JarFile jar = new JarFile(signedFile); + JarEntry entry = new JarEntry(entryName3); + entry.setSize(1076); + InputStream in = jar.getInputStream(entry); + // BEGIN android-added + byte[] dummy = getAllBytesFromStream(in); + // END android-added + fail("SecurityException should be thrown."); + } catch (SecurityException e) { + // expected + } catch (Exception e) { + fail("Exception during test 5: " + e); + } + + try { + Support_Resources.copyFile(resources, null, jarName5); + signedFile = new File(resources, jarName5); + } catch (Exception e) { + fail("Failed to create local file 5: " + e); + } + + try { + JarFile jar = new JarFile(signedFile); + JarEntry entry = new JarEntry(entryName3); + InputStream in = jar.getInputStream(entry); + fail("SecurityException should be thrown."); + } catch (SecurityException e) { + // expected + } catch (Exception e) { + fail("Exception during test 5: " + e); + } + } + + /* + * The jar created by 1.4 which does not provide a + * algorithm-Digest-Manifest-Main-Attributes entry in .SF file. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "entries", + args = {} + ) + public void test_Jar_created_before_java_5() throws IOException { + String modifiedJarName = "Created_by_1_4.jar"; + Support_Resources.copyFile(resources, null, modifiedJarName); + JarFile jarFile = new JarFile(new File(resources, modifiedJarName), + true); + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry zipEntry = entries.nextElement(); + jarFile.getInputStream(zipEntry); + } + } + + /* The jar is intact, then everything is all right. */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "entries", + args = {} + ) + public void test_JarFile_Integrate_Jar() throws IOException { + String modifiedJarName = "Integrate.jar"; + Support_Resources.copyFile(resources, null, modifiedJarName); + JarFile jarFile = new JarFile(new File(resources, modifiedJarName), + true); + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry zipEntry = entries.nextElement(); + jarFile.getInputStream(zipEntry); + } + } + + /* + * If another entry is inserted into Manifest, no security exception will be + * thrown out. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "entries", + args = {} + ) + public void test_JarFile_InsertEntry_in_Manifest_Jar() throws IOException { + String modifiedJarName = "Inserted_Entry_Manifest.jar"; + Support_Resources.copyFile(resources, null, modifiedJarName); + JarFile jarFile = new JarFile(new File(resources, modifiedJarName), + true); + Enumeration<JarEntry> entries = jarFile.entries(); + int count = 0; + while (entries.hasMoreElements()) { + + ZipEntry zipEntry = entries.nextElement(); + jarFile.getInputStream(zipEntry); + count++; + } + assertEquals(5, count); + } + + /* + * If another entry is inserted into Manifest, no security exception will be + * thrown out. + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "entries", + args = {} + ) + public void test_Inserted_Entry_Manifest_with_DigestCode() + throws IOException { + String modifiedJarName = "Inserted_Entry_Manifest_with_DigestCode.jar"; + Support_Resources.copyFile(resources, null, modifiedJarName); + JarFile jarFile = new JarFile(new File(resources, modifiedJarName), + true); + Enumeration<JarEntry> entries = jarFile.entries(); + int count = 0; + while (entries.hasMoreElements()) { + + ZipEntry zipEntry = entries.nextElement(); + jarFile.getInputStream(zipEntry); + count++; + } + assertEquals(5, count); + } + + /* + * The content of Test.class is modified, jarFile.getInputStream will not + * throw security Exception, but it will anytime before the inputStream got + * from getInputStream method has been read to end. + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "SecurityException and functionality checked.", + method = "getInputStream", + args = {java.util.zip.ZipEntry.class} + ) + public void test_JarFile_Modified_Class() throws IOException { + String modifiedJarName = "Modified_Class.jar"; + Support_Resources.copyFile(resources, null, modifiedJarName); + JarFile jarFile = new JarFile(new File(resources, modifiedJarName), + true); + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry zipEntry = entries.nextElement(); + jarFile.getInputStream(zipEntry); + } + /* The content of Test.class has been tampered. */ + ZipEntry zipEntry = jarFile.getEntry("Test.class"); + InputStream in = jarFile.getInputStream(zipEntry); + byte[] buffer = new byte[1024]; + try { + while (in.available() > 0) { + in.read(buffer); + } + fail("should throw Security Exception"); + } catch (SecurityException e) { + // desired + } + } + + /* + * In the Modified.jar, the main attributes of META-INF/MANIFEST.MF is + * tampered manually. Hence the RI 5.0 JarFile.getInputStream of any + * JarEntry will throw security exception, but the apache harmony will not. + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "SecurityException and functionality checked.", + method = "getInputStream", + args = {java.util.zip.ZipEntry.class} + ) + public void test_JarFile_Modified_Manifest_MainAttributes() + throws IOException { + String modifiedJarName = "Modified_Manifest_MainAttributes.jar"; + Support_Resources.copyFile(resources, null, modifiedJarName); + JarFile jarFile = new JarFile(new File(resources, modifiedJarName), + true); + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry zipEntry = entries.nextElement(); + try { + jarFile.getInputStream(zipEntry); + fail("should throw Security Exception"); + } catch (SecurityException e) { + // desired + } + } + } + + /* + * It is all right in our original JarFile. If the Entry Attributes, for + * example Test.class in our jar, the jarFile.getInputStream will throw + * Security Exception. + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "SecurityException and functionality checked.", + method = "getInputStream", + args = {java.util.zip.ZipEntry.class} + ) + public void test_JarFile_Modified_Manifest_EntryAttributes() + throws IOException { + String modifiedJarName = "Modified_Manifest_EntryAttributes.jar"; + Support_Resources.copyFile(resources, null, modifiedJarName); + JarFile jarFile = new JarFile(new File(resources, modifiedJarName), + true); + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry zipEntry = entries.nextElement(); + try { + jarFile.getInputStream(zipEntry); + fail("should throw Security Exception"); + } catch (SecurityException e) { + // desired + } + } + } + + /* + * If the content of the .SA file is modified, no matter what it resides, + * JarFile.getInputStream of any JarEntry will throw Security Exception. + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "SecurityException and functionality checked.", + method = "getInputStream", + args = {java.util.zip.ZipEntry.class} + ) + public void test_JarFile_Modified_SF_EntryAttributes() throws IOException { + String modifiedJarName = "Modified_SF_EntryAttributes.jar"; + Support_Resources.copyFile(resources, null, modifiedJarName); + JarFile jarFile = new JarFile(new File(resources, modifiedJarName), + true); + Enumeration<JarEntry> entries = jarFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry zipEntry = entries.nextElement(); + try { + jarFile.getInputStream(zipEntry); + fail("should throw Security Exception"); + } catch (SecurityException e) { + // desired + } + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "close", + args = {} + ) + public void test_close() throws IOException { + String modifiedJarName = "Modified_SF_EntryAttributes.jar"; + Support_Resources.copyFile(resources, null, modifiedJarName); + JarFile jarFile = new JarFile(new File(resources, modifiedJarName), + true); + Enumeration<JarEntry> entries = jarFile.entries(); + + jarFile.close(); + jarFile.close(); + + // Can not check IOException + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarInputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarInputStreamTest.java new file mode 100644 index 0000000..1a8c753 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarInputStreamTest.java @@ -0,0 +1,653 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import tests.support.resource.Support_Resources; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarInputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; + +@TestTargetClass(JarInputStream.class) +public class JarInputStreamTest extends junit.framework.TestCase { + // a 'normal' jar file + private String jarName; + + // same as patch.jar but without a manifest file + private String jarName2; + + private final String entryName = "foo/bar/A.class"; + + private static final int indexofDSA = 2; + + private static final int indexofTESTCLASS = 4; + + private static final int totalEntries = 4; + + @Override + protected void setUp() { + jarName = Support_Resources.getURL("morestuff/hyts_patch.jar"); + jarName2 = Support_Resources.getURL("morestuff/hyts_patch2.jar"); + } + + /** + * @tests java.util.jar.JarInputStream#JarInputStream(java.io.InputStream) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarInputStream", + args = {java.io.InputStream.class} + ) + public void test_ConstructorLjava_io_InputStream() { + // Test for method java.util.jar.JarInputStream(java.io.InputStream) + InputStream is = null; + JarInputStream jis = null; + try { + is = new URL(jarName).openConnection().getInputStream(); + boolean hasCorrectEntry = false; + jis = new JarInputStream(is); + assertNotNull("The jar input stream should have a manifest", jis + .getManifest()); + JarEntry je = jis.getNextJarEntry(); + while (je != null) { + if (je.getName().equals(entryName)) { + hasCorrectEntry = true; + } + je = jis.getNextJarEntry(); + } + assertTrue( + "The jar input stream does not contain the correct entries", + hasCorrectEntry); + } catch (Exception e) { + fail("Exception during test: " + e.toString()); + } + + try { + is.close(); + jis = new JarInputStream(is); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + /** + * @tests java.util.jar.JarInputStream#getManifest() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getManifest", + args = {} + ) + public void test_getManifest() { + // Test for method java.util.jar.Manifest + // java.util.jar.JarInputStream.getManifest() + try { + Manifest m; + + InputStream is = new URL(jarName2).openConnection() + .getInputStream(); + JarInputStream jis = new JarInputStream(is); + m = jis.getManifest(); + assertNull("The jar input stream should not have a manifest", m); + + is = new URL(jarName).openConnection().getInputStream(); + jis = new JarInputStream(is); + m = jis.getManifest(); + assertNotNull("The jar input stream should have a manifest", m); + } catch (Exception e) { + fail("Exception during test: " + e.toString()); + } + + } + + /** + * @tests java.util.jar.JarInputStream#getNextJarEntry() + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "getNextJarEntry", + args = {} + ) + public void test_getNextJarEntry() throws Exception { + final Set<String> desired = new HashSet<String>(Arrays + .asList(new String[] { + "foo/", "foo/bar/", "foo/bar/A.class", "Blah.txt"})); + Set<String> actual = new HashSet<String>(); + InputStream is = new URL(jarName).openConnection().getInputStream(); + JarInputStream jis = new JarInputStream(is); + JarEntry je = jis.getNextJarEntry(); + while (je != null) { + actual.add(je.toString()); + je = jis.getNextJarEntry(); + } + assertEquals(actual, desired); + jis.close(); + +// try { +// jis.getNextJarEntry(); //Android implementation does not throw exception +// fail("IOException expected"); +// } catch (IOException ee) { +// // expected +// } + + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_entry.jar"); + is = Support_Resources.getStream("Broken_entry.jar"); + jis = new JarInputStream(is, false); + jis.getNextJarEntry(); + try { + jis.getNextJarEntry(); + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "getNextJarEntry", + args = {} + ) + @KnownFailure("IOException not thrown when using getNextJarEntry() after close().") + // This is the original test_getNextJarEntry including the failing section. + public void test_getNextJarEntry_Ex() throws Exception { + final Set<String> desired = new HashSet<String>(Arrays + .asList(new String[] { + "foo/", "foo/bar/", "foo/bar/A.class", "Blah.txt"})); + Set<String> actual = new HashSet<String>(); + InputStream is = new URL(jarName).openConnection().getInputStream(); + JarInputStream jis = new JarInputStream(is); + JarEntry je = jis.getNextJarEntry(); + while (je != null) { + actual.add(je.toString()); + je = jis.getNextJarEntry(); + } + assertEquals(actual, desired); + jis.close(); + + try { + jis.getNextJarEntry(); //Android implementation does not throw exception + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_entry.jar"); + is = Support_Resources.getStream("Broken_entry.jar"); + jis = new JarInputStream(is, false); + jis.getNextJarEntry(); + try { + jis.getNextJarEntry(); + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Exceptions checking missed. Case2", + method = "getNextJarEntry", + args = {} + ) + public void test_JarInputStream_Integrate_Jar_getNextEntry() + throws IOException { + String intJarName = Support_Resources.getURL("Integrate.jar"); + InputStream is = new URL(intJarName).openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is, true); + ZipEntry entry = null; + int count = 0; + while (count == 0 || entry != null) { + count++; + entry = jin.getNextEntry(); + } + assertEquals(totalEntries + 1, count); + jin.close(); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException & ZipException checking missed.", + method = "getNextEntry", + args = {} + ) + public void test_JarInputStream_Modified_Class_getNextEntry() + throws IOException { + String modJarName = Support_Resources.getURL("Modified_Class.jar"); + InputStream is = new URL(modJarName).openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is, true); + ZipEntry zipEntry = null; + + int count = 0; + while (count == 0 || zipEntry != null) { + count++; + try { + zipEntry = jin.getNextEntry(); + if (count == indexofTESTCLASS + 1) { + fail("Should throw Security Exception"); + } + } catch (SecurityException e) { + if (count != indexofTESTCLASS + 1) { + throw e; + } + + } + } + assertEquals(totalEntries + 2, count); + jin.close(); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException & ZipException checking missed.", + method = "getNextEntry", + args = {} + ) + public void test_JarInputStream_Modified_Manifest_MainAttributes_getNextEntry() + throws IOException { + String modJarName = Support_Resources + .getURL("Modified_Manifest_MainAttributes.jar"); + InputStream is = new URL(modJarName).openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is, true); + ZipEntry zipEntry = null; + + final int indexofDSA = 2; + final int totalEntries = 4; + int count = 0; + while (count == 0 || zipEntry != null) { + count++; + try { + zipEntry = jin.getNextEntry(); + if (count == indexofDSA + 1) { + fail("Should throw Security Exception"); + } + } catch (SecurityException e) { + if (count != indexofDSA + 1) { + throw e; + } + } + } + assertEquals(totalEntries + 2, count); + jin.close(); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException & ZipException checking missed.", + method = "getNextEntry", + args = {} + ) + public void test_JarInputStream_Modified_Manifest_EntryAttributes_getNextEntry() + throws IOException { + String modJarName = Support_Resources + .getURL("Modified_Manifest_EntryAttributes.jar"); + InputStream is = new URL(modJarName).openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is, true); + ZipEntry zipEntry = null; + + int count = 0; + while (count == 0 || zipEntry != null) { + count++; + try { + zipEntry = jin.getNextEntry(); + if (count == indexofDSA + 1) { + fail("Should throw Security Exception"); + } + } catch (SecurityException e) { + if (count != indexofDSA + 1) { + throw e; + } + } + } + assertEquals(totalEntries + 2, count); + jin.close(); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException & ZipException checking missed.", + method = "getNextEntry", + args = {} + ) + public void test_JarInputStream_Modified_SF_EntryAttributes_getNextEntry() + throws IOException { + String modJarName = Support_Resources + .getURL("Modified_SF_EntryAttributes.jar"); + InputStream is = new URL(modJarName).openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is, true); + ZipEntry zipEntry = null; + + int count = 0; + while (count == 0 || zipEntry != null) { + count++; + try { + zipEntry = jin.getNextEntry(); + if (count == indexofDSA + 1) { + fail("Should throw Security Exception"); + } + } catch (SecurityException e) { + if (count != indexofDSA + 1) { + throw e; + } + } + } + assertEquals(totalEntries + 2, count); + jin.close(); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException & ZipException checking missed.", + method = "read", + args = {byte[].class} + ) + public void test_JarInputStream_Modified_Class_read() throws IOException { + String modJarName = Support_Resources.getURL("Modified_Class.jar"); + InputStream is = new URL(modJarName).openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is, true); + int count = 0; + ZipEntry zipEntry = null; + while (count == 0 || zipEntry != null) { + count++; + zipEntry = jin.getNextEntry(); + byte[] buffer = new byte[1024]; + try { + int length = 0; + while (length >= 0) { + length = jin.read(buffer); + } + if (count == indexofTESTCLASS) { + fail("Should throw Security Exception"); + } + } catch (SecurityException e) { + if (count < indexofTESTCLASS) { + throw e; + } + } + } + assertEquals(totalEntries + 1, count); + jin.close(); + } + + @TestTargetNew( + level = TestLevel.PARTIAL, + notes = "Exception checking missed.", + method = "read", + args = {byte[].class} + ) + public void test_Integrate_Jar_read() throws IOException { + String intJarName = Support_Resources.getURL("Integrate.jar"); + InputStream is = new URL(intJarName).openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is, true); + int count = 0; + ZipEntry zipEntry = null; + while (count == 0 || zipEntry != null) { + count++; + zipEntry = jin.getNextEntry(); + byte[] buffer = new byte[1024]; + int length = 0; + while (length >= 0) { + length = jin.read(buffer); + } + + } + assertEquals(totalEntries + 1, count); + jin.close(); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException & ZipException checking missed.", + method = "read", + args = {byte[].class} + ) + public void test_JarInputStream_Modified_Manifest_MainAttributes_read() + throws IOException { + String modJarName = Support_Resources + .getURL("Modified_Manifest_MainAttributes.jar"); + InputStream is = new URL(modJarName).openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is, true); + int count = 0; + ZipEntry zipEntry = null; + while (count == 0 || zipEntry != null) { + count++; + zipEntry = jin.getNextEntry(); + byte[] buffer = new byte[1024]; + try { + int length = 0; + while (length >= 0) { + length = jin.read(buffer); + } + if (count == indexofDSA) { + fail("Should throw Security Exception"); + } + } catch (SecurityException e) { + if (count != indexofDSA) { + throw e; + } + } + } + assertEquals(totalEntries + 1, count); + jin.close(); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException & ZipException checking missed.", + method = "read", + args = {byte[].class} + ) + public void test_JarInputStream_Modified_SF_EntryAttributes_read() + throws IOException { + String modJarName = Support_Resources + .getURL("Modified_SF_EntryAttributes.jar"); + InputStream is = new URL(modJarName).openConnection().getInputStream(); + JarInputStream jin = new JarInputStream(is, true); + int count = 0; + ZipEntry zipEntry = null; + while (count == 0 || zipEntry != null) { + count++; + zipEntry = jin.getNextEntry(); + byte[] buffer = new byte[1024]; + try { + int length = 0; + while (length >= 0) { + length = jin.read(buffer); + } + if (count == indexofDSA) { + fail("Should throw Security Exception"); + } + } catch (SecurityException e) { + if (count != indexofDSA) { + throw e; + } + } + } + assertEquals(totalEntries + 1, count); + jin.close(); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "JarInputStream", + args = {java.io.InputStream.class, boolean.class} + ) + public void test_ConstructorLjava_io_InputStreamZ() { + // Test for method java.util.jar.JarInputStream(java.io.InputStream) + InputStream is = null; + JarInputStream jis = null; + try { + is = new URL(jarName).openConnection().getInputStream(); + boolean hasCorrectEntry = false; + jis = new JarInputStream(is, true); + assertNotNull("The jar input stream should have a manifest", jis + .getManifest()); + JarEntry je = jis.getNextJarEntry(); + while (je != null) { + if (je.getName().equals(entryName)) { + hasCorrectEntry = true; + } + je = jis.getNextJarEntry(); + } + assertTrue( + "The jar input stream does not contain the correct entries", + hasCorrectEntry); + } catch (Exception e) { + fail("Exception during test: " + e.toString()); + } + + try { + is.close(); + jis = new JarInputStream(is, false); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "close", + args = {} + ) + @KnownFailure("The behaviour is different from RI, but not neccessarily wrong. However a strange exception message is given anyway!") + public void test_closeAfterException() throws Exception { + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_entry.jar"); + InputStream is = Support_Resources.getStream("Broken_entry.jar"); + JarInputStream jis = new JarInputStream(is, false); + jis.getNextEntry(); + try { + jis.getNextEntry(); + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + jis.close(); // Android throws exception here, but RI throws when getNextEntry/read/skip are called. + try { + jis.getNextEntry(); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "getNextEntry", + args = {} + ) + public void test_getNextEntry() throws Exception { + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_entry.jar"); + InputStream is = Support_Resources.getStream("Broken_entry.jar"); + JarInputStream jis = new JarInputStream(is, false); + jis.getNextEntry(); + try { + jis.getNextEntry(); + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + + try { + jis.close(); // Android throws exception here, already! + jis.getNextEntry(); // But RI here, only! + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + class Mock_JarInputStream extends JarInputStream { + + public Mock_JarInputStream(InputStream in) throws IOException { + super(in); + } + + public ZipEntry createZipEntry(String str) { + return super.createZipEntry(str); + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "createZipEntry", + args = {java.lang.String.class} + ) + public void test_createZipEntryLjava_lang_String() throws Exception { + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_entry.jar"); + InputStream is = Support_Resources.getStream("Broken_entry.jar"); + Mock_JarInputStream mjis = new Mock_JarInputStream(is); + assertNotNull(mjis.createZipEntry("New entry")); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "read", + args = {byte[].class, int.class, int.class} + ) + public void test_read$ZII() throws Exception { + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_entry_data.jar"); + InputStream is = Support_Resources.getStream("Broken_entry_data.jar"); + JarInputStream jis = new JarInputStream(is, true); + byte b[] = new byte[100]; + + jis.getNextEntry(); + jis.read(b, 0, 100); + jis.getNextEntry(); + jis.getNextEntry(); + jis.getNextEntry(); + + try { + jis.read(b, 0, 100); + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + + try { + jis.close(); // Android throws exception here, already! + jis.read(b, 0, 100); // But RI here, only! + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarOutputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarOutputStreamTest.java new file mode 100644 index 0000000..e652137 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarOutputStreamTest.java @@ -0,0 +1,209 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URL; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; + +import tests.support.Support_Exec; +import tests.support.resource.Support_Resources; + +@TestTargetClass(JarOutputStream.class) +public class JarOutputStreamTest extends junit.framework.TestCase { + + /** + * @tests java.util.jar.JarOutputStream#putNextEntry(java.util.zip.ZipEntry) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "putNextEntry", + args = {java.util.zip.ZipEntry.class} + ) + public void test_putNextEntryLjava_util_zip_ZipEntry() { + // testClass file`s actual extension is .class, since having .class + // extension files in source dir causes + // problems on eclipse, the extension is changed into .ser or it can be + // anything. The file is being + // read by inputstream and being written to other file, + // as long as the content of the file is not changed, the extension does + // not matter + final String testClass = "hyts_mainClass.ser"; + final String entryName = "foo/bar/execjartest/MainClass.class"; + + // test whether specifying the main class in the manifest + // works using either /'s or .'s as a separator + final String[] manifestMain = { + "foo.bar.execjartest.MainClass", + "foo/bar/execjartest/MainClass"}; + + for (String element : manifestMain) { + + // create the manifest + Manifest newman = new Manifest(); + Attributes att = newman.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, element); + + File outputJar = null; + JarOutputStream jout = null; + + try { + // open the output jarfile + outputJar = File.createTempFile("hyts_", ".jar"); + jout = new JarOutputStream(new FileOutputStream(outputJar), + newman); + jout.putNextEntry(new JarEntry(entryName)); + } catch (Exception e) { + fail("Error creating JarOutputStream: " + e); + } + File resources = Support_Resources.createTempFolder(); + try { + // read in the class file, and output it to the jar + Support_Resources.copyFile(resources, null, testClass); + URL jarURL = new URL((new File(resources, testClass)).toURL() + .toString()); + InputStream jis = jarURL.openStream(); + + byte[] bytes = new byte[1024]; + int len; + while ((len = jis.read(bytes)) != -1) { + jout.write(bytes, 0, len); + } + + jout.flush(); + jout.close(); + jis.close(); + } catch (Exception e) { + fail("Error writing JAR file for testing: " + e); + } + String res = null; + // set up the VM parameters + String[] args = new String[2]; + args[0] = "-jar"; + args[1] = outputJar.getAbsolutePath(); + +// It's not that simple to execute a JAR against Dalvik VM (see DalvikExecTest): +// +// try { +// // execute the JAR and read the result +// res = Support_Exec.execJava(args, null, true); +// } catch (Exception e) { +// fail("Exception executing test JAR: " + e); +// } +// +// assertTrue("Error executing JAR test on: " + element +// + ". Result returned was incorrect.", res +// .startsWith("TEST")); + outputJar.delete(); + + try { + // open the output jarfile + outputJar = File.createTempFile("hyts_", ".jar"); + OutputStream os = new FileOutputStream(outputJar); + jout = new JarOutputStream(os, newman); + os.close(); + jout.putNextEntry(new JarEntry(entryName)); + fail("IOException expected"); + } catch (IOException e) { + // expected + } + } + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Checks IOException", + method = "JarOutputStream", + args = {java.io.OutputStream.class, java.util.jar.Manifest.class} + ) + public void test_JarOutputStreamLjava_io_OutputStreamLjava_util_jar_Manifest() + throws IOException { + File fooJar = File.createTempFile("hyts_", ".jar"); + File barZip = File.createTempFile("hyts_", ".zip"); + + FileOutputStream fos = new FileOutputStream(fooJar); + + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + att.put(Attributes.Name.CLASS_PATH, barZip.getName()); + + fos.close(); + try { + JarOutputStream joutFoo = new JarOutputStream(fos, man); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Can not check IOException", + method = "JarOutputStream", + args = {java.io.OutputStream.class} + ) + public void test_JarOutputStreamLjava_io_OutputStream() throws IOException { + File fooJar = File.createTempFile("hyts_", ".jar"); + + FileOutputStream fos = new FileOutputStream(fooJar); + ZipEntry ze = new ZipEntry("Test"); + + try { + JarOutputStream joutFoo = new JarOutputStream(fos); + joutFoo.putNextEntry(ze); + joutFoo.write(33); + } catch (IOException ee) { + fail("IOException is not expected"); + } + + fos.close(); + fooJar.delete(); + try { + JarOutputStream joutFoo = new JarOutputStream(fos); + joutFoo.putNextEntry(ze); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + @Override + protected void setUp() { + } + + @Override + protected void tearDown() { + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ManifestTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ManifestTest.java new file mode 100644 index 0000000..57e4744 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ManifestTest.java @@ -0,0 +1,513 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +import junit.framework.TestCase; +import tests.support.resource.Support_Resources; + +@TestTargetClass(Manifest.class) +public class ManifestTest extends TestCase { + + private final String jarName = "hyts_patch.jar"; + + private final String attJarName = "hyts_att.jar"; + + private Manifest m; + + private Manifest m2; + + private File resources; + + @Override + protected void setUp() { + resources = Support_Resources.createTempFolder(); + try { + Support_Resources.copyFile(resources, null, jarName); + JarFile jarFile = new JarFile(new File(resources, jarName)); + m = jarFile.getManifest(); + jarFile.close(); + Support_Resources.copyFile(resources, null, attJarName); + jarFile = new JarFile(new File(resources, attJarName)); + m2 = jarFile.getManifest(); + jarFile.close(); + } catch (Exception e) { + fail("Exception during setup: " + e.toString()); + } + } + + /** + * @tests java.util.jar.Manifest#Manifest() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Manifest", + args = {} + ) + public void test_Constructor() { + // Test for method java.util.jar.Manifest() + Manifest emptyManifest = new Manifest(); + assertTrue("Should have no entries", emptyManifest.getEntries() + .isEmpty()); + assertTrue("Should have no main attributes", emptyManifest + .getMainAttributes().isEmpty()); + } + + /** + * @tests java.util.jar.Manifest#Manifest(java.io.InputStream) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "IOException checking missed.", + method = "Manifest", + args = {java.io.InputStream.class} + ) + public void test_ConstructorLjava_io_InputStream() { + // Test for method java.util.jar.Manifest(java.io.InputStream) + /* + * ByteArrayOutputStream baos = new ByteArrayOutputStream(); + * m2.write(baos); InputSteam is = new ByteArrayInputStream + * (baos.toByteArray()); Manifest myManifest = new Manifest (is); + * assertTrue("Manifests should be equal", myManifest.equals(m2)); + */ + + Manifest manifest = null; + InputStream is = null; + try { + is = new URL(Support_Resources.getURL("manifest/hyts_MANIFEST.MF")) + .openStream(); + } catch (MalformedURLException e1) { + fail("Failed to create InputStream object"); + } catch (IOException e1) { + fail("Failed to create InputStream object"); + } + try { + manifest = new Manifest(is); + } catch (MalformedURLException e) { + fail("Malformed URL"); + } catch (IOException e) { + fail("IOException"); + } + Attributes main = manifest.getMainAttributes(); + assertEquals("Bundle-Name not correct", "ClientSupport", main + .getValue("Bundle-Name")); + assertEquals( + "Bundle-Description not correct", + + "Provides SessionService, AuthenticationService. Extends RegistryService.", + main.getValue("Bundle-Description")); + assertEquals("Bundle-Activator not correct", + "com.ibm.ive.eccomm.client.support.ClientSupportActivator", + main.getValue("Bundle-Activator")); + assertEquals( + "Import-Package not correct", + + "com.ibm.ive.eccomm.client.services.log,com.ibm.ive.eccomm.client.services.registry,com.ibm.ive.eccomm.service.registry; specification-version=1.0.0,com.ibm.ive.eccomm.service.session; specification-version=1.0.0,com.ibm.ive.eccomm.service.framework; specification-version=1.2.0,org.osgi.framework; specification-version=1.0.0,org.osgi.service.log; specification-version=1.0.0,com.ibm.ive.eccomm.flash; specification-version=1.2.0,com.ibm.ive.eccomm.client.xml,com.ibm.ive.eccomm.client.http.common,com.ibm.ive.eccomm.client.http.client", + main.getValue("Import-Package")); + assertEquals( + "Import-Service not correct", + + "org.osgi.service.log.LogReaderServiceorg.osgi.service.log.LogService,com.ibm.ive.eccomm.service.registry.RegistryService", + main.getValue("Import-Service")); + assertEquals( + "Export-Package not correct", + + "com.ibm.ive.eccomm.client.services.authentication; specification-version=1.0.0,com.ibm.ive.eccomm.service.authentication; specification-version=1.0.0,com.ibm.ive.eccomm.common; specification-version=1.0.0,com.ibm.ive.eccomm.client.services.registry.store; specification-version=1.0.0", + main.getValue("Export-Package")); + assertEquals( + "Export-Service not correct", + + "com.ibm.ive.eccomm.service.authentication.AuthenticationService,com.ibm.ive.eccomm.service.session.SessionService", + main.getValue("Export-Service")); + assertEquals("Bundle-Vendor not correct", "IBM", main + .getValue("Bundle-Vendor")); + assertEquals("Bundle-Version not correct", "1.2.0", main + .getValue("Bundle-Version")); + try { + is.close(); + } catch (IOException e1) { + fail("Failed to close InputStream object"); + } + try { + manifest = new Manifest(is); + fail("IOException expected"); + } catch (MalformedURLException e) { + fail("IOException expected"); + } catch (IOException e) { + // expected + } + } + + /** + * @tests java.util.jar.Manifest#clear() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clear", + args = {} + ) + public void test_clear() { + // Test for method void java.util.jar.Manifest.clear() + m2.clear(); + assertTrue("Should have no entries", m2.getEntries().isEmpty()); + assertTrue("Should have no main attributes", m2.getMainAttributes() + .isEmpty()); + } + + /** + * @tests java.util.jar.Manifest#getAttributes(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAttributes", + args = {java.lang.String.class} + ) + public void test_getAttributesLjava_lang_String() { + // Test for method java.util.jar.Attributes + // java.util.jar.Manifest.getAttributes(java.lang.String) + assertNull("Should not exist", m2.getAttributes("Doesn't Exist")); + assertEquals("Should exist", "OK", m2 + .getAttributes("HasAttributes.txt").get( + new Attributes.Name("MyAttribute"))); + } + + /** + * @tests java.util.jar.Manifest#getEntries() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getEntries", + args = {} + ) + public void test_getEntries() { + // Test for method java.util.Map java.util.jar.Manifest.getEntries() + Map<String, Attributes> myMap = m2.getEntries(); + assertNull("Shouldn't exist", myMap.get("Doesn't exist")); + assertEquals("Should exist", "OK", myMap.get("HasAttributes.txt").get( + new Attributes.Name("MyAttribute"))); + + } + + /** + * @tests java.util.jar.Manifest#getMainAttributes() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMainAttributes", + args = {} + ) + public void test_getMainAttributes() { + // Test for method java.util.jar.Attributes + // java.util.jar.Manifest.getMainAttributes() + Attributes a = m.getMainAttributes(); + assertEquals("Manifest_Version should return 1.0", "1.0", a + .get(Attributes.Name.MANIFEST_VERSION)); + } + + /** + * @tests {@link java.util.jar.Manifest#read(java.io.InputStream) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "read", + args = {java.io.InputStream.class} + ) + public void test_readLjava_io_InputStream() { + // Regression for HARMONY-89 + InputStream is = new InputStreamImpl(); + try { + new Manifest().read(is); + fail("Assert 0: Should have thrown IOException"); + } catch (IOException e) { + // expected + } + + Manifest manifest = new Manifest(); + try { + manifest.read(new URL(Support_Resources + .getURL("manifest/hyts_MANIFEST.MF")).openStream()); + } catch (MalformedURLException e) { + fail("Can nor read manifest"); + } catch (IOException e) { + fail("Can nor read manifest"); + } + Attributes main = manifest.getMainAttributes(); + assertEquals("Bundle-Name not correct", "ClientSupport", main + .getValue("Bundle-Name")); + assertEquals( + "Bundle-Description not correct", + + "Provides SessionService, AuthenticationService. Extends RegistryService.", + main.getValue("Bundle-Description")); + assertEquals("Bundle-Activator not correct", + "com.ibm.ive.eccomm.client.support.ClientSupportActivator", + main.getValue("Bundle-Activator")); + assertEquals( + "Import-Package not correct", + + "com.ibm.ive.eccomm.client.services.log,com.ibm.ive.eccomm.client.services.registry,com.ibm.ive.eccomm.service.registry; specification-version=1.0.0,com.ibm.ive.eccomm.service.session; specification-version=1.0.0,com.ibm.ive.eccomm.service.framework; specification-version=1.2.0,org.osgi.framework; specification-version=1.0.0,org.osgi.service.log; specification-version=1.0.0,com.ibm.ive.eccomm.flash; specification-version=1.2.0,com.ibm.ive.eccomm.client.xml,com.ibm.ive.eccomm.client.http.common,com.ibm.ive.eccomm.client.http.client", + main.getValue("Import-Package")); + assertEquals( + "Import-Service not correct", + + "org.osgi.service.log.LogReaderServiceorg.osgi.service.log.LogService,com.ibm.ive.eccomm.service.registry.RegistryService", + main.getValue("Import-Service")); + assertEquals( + "Export-Package not correct", + + "com.ibm.ive.eccomm.client.services.authentication; specification-version=1.0.0,com.ibm.ive.eccomm.service.authentication; specification-version=1.0.0,com.ibm.ive.eccomm.common; specification-version=1.0.0,com.ibm.ive.eccomm.client.services.registry.store; specification-version=1.0.0", + main.getValue("Export-Package")); + assertEquals( + "Export-Service not correct", + + "com.ibm.ive.eccomm.service.authentication.AuthenticationService,com.ibm.ive.eccomm.service.session.SessionService", + main.getValue("Export-Service")); + assertEquals("Bundle-Vendor not correct", "IBM", main + .getValue("Bundle-Vendor")); + assertEquals("Bundle-Version not correct", "1.2.0", main + .getValue("Bundle-Version")); + } + + // helper class + class InputStreamImpl extends InputStream { + public InputStreamImpl() { + super(); + } + + @Override + public int read() { + return 0; + } + } + + /** + * @tests java.util.jar.Manifest#Manifest(Manifest) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Manifest", + args = {java.util.jar.Manifest.class} + ) + public void test_ConstructorLjava_util_jar_Manifest() { + // Test for method java.util.jar.Manifest() + Manifest emptyManifest = new Manifest(); + Manifest emptyClone = new Manifest(emptyManifest); + assertTrue("Should have no entries", emptyClone.getEntries().isEmpty()); + assertTrue("Should have no main attributes", emptyClone + .getMainAttributes().isEmpty()); + assertEquals(emptyClone, emptyManifest); + assertEquals(emptyClone, emptyManifest.clone()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + Manifest emptyManifest = new Manifest(); + Manifest emptyClone = (Manifest) emptyManifest.clone(); + assertTrue("Should have no entries", emptyClone.getEntries().isEmpty()); + assertTrue("Should have no main attributes", emptyClone + .getMainAttributes().isEmpty()); + assertEquals(emptyClone, emptyManifest); + assertEquals(emptyManifest.clone().getClass().getName(), + "java.util.jar.Manifest"); + + Manifest manifest = null; + try { + manifest = new Manifest(new URL(Support_Resources + .getURL("manifest/hyts_MANIFEST.MF")).openStream()); + } catch (MalformedURLException e) { + fail("Malformed URL"); + } catch (IOException e) { + fail("IOException"); + } + Manifest manifestClone = (Manifest) manifest.clone(); + Attributes main = manifestClone.getMainAttributes(); + assertEquals("Bundle-Name not correct", "ClientSupport", main + .getValue("Bundle-Name")); + assertEquals( + "Bundle-Description not correct", + + "Provides SessionService, AuthenticationService. Extends RegistryService.", + main.getValue("Bundle-Description")); + assertEquals("Bundle-Activator not correct", + "com.ibm.ive.eccomm.client.support.ClientSupportActivator", + main.getValue("Bundle-Activator")); + assertEquals( + "Import-Package not correct", + + "com.ibm.ive.eccomm.client.services.log,com.ibm.ive.eccomm.client.services.registry,com.ibm.ive.eccomm.service.registry; specification-version=1.0.0,com.ibm.ive.eccomm.service.session; specification-version=1.0.0,com.ibm.ive.eccomm.service.framework; specification-version=1.2.0,org.osgi.framework; specification-version=1.0.0,org.osgi.service.log; specification-version=1.0.0,com.ibm.ive.eccomm.flash; specification-version=1.2.0,com.ibm.ive.eccomm.client.xml,com.ibm.ive.eccomm.client.http.common,com.ibm.ive.eccomm.client.http.client", + main.getValue("Import-Package")); + assertEquals( + "Import-Service not correct", + + "org.osgi.service.log.LogReaderServiceorg.osgi.service.log.LogService,com.ibm.ive.eccomm.service.registry.RegistryService", + main.getValue("Import-Service")); + assertEquals( + "Export-Package not correct", + + "com.ibm.ive.eccomm.client.services.authentication; specification-version=1.0.0,com.ibm.ive.eccomm.service.authentication; specification-version=1.0.0,com.ibm.ive.eccomm.common; specification-version=1.0.0,com.ibm.ive.eccomm.client.services.registry.store; specification-version=1.0.0", + main.getValue("Export-Package")); + assertEquals( + "Export-Service not correct", + + "com.ibm.ive.eccomm.service.authentication.AuthenticationService,com.ibm.ive.eccomm.service.session.SessionService", + main.getValue("Export-Service")); + assertEquals("Bundle-Vendor not correct", "IBM", main + .getValue("Bundle-Vendor")); + assertEquals("Bundle-Version not correct", "1.2.0", main + .getValue("Bundle-Version")); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "equals", + args = {java.lang.Object.class} + ) + public void test_equals() { + Manifest manifest1 = null; + Manifest manifest2 = null; + Manifest manifest3 = new Manifest(); + InputStream is = null; + try { + is = new URL(Support_Resources.getURL("manifest/hyts_MANIFEST.MF")) + .openStream(); + } catch (MalformedURLException e1) { + fail("Failed to create InputStream object"); + } catch (IOException e1) { + fail("Failed to create InputStream object"); + } + try { + manifest1 = new Manifest(is); + } catch (MalformedURLException e) { + fail("Malformed URL"); + } catch (IOException e) { + fail("IOException"); + } + + try { + manifest2 = new Manifest(new URL(Support_Resources + .getURL("manifest/hyts_MANIFEST.MF")).openStream()); + } catch (MalformedURLException e) { + fail("Malformed URL"); + } catch (IOException e) { + fail("IOException"); + } + + assertTrue(manifest1.equals(manifest1)); + assertTrue(manifest1.equals(manifest2)); + assertFalse(manifest1.equals(manifest3)); + assertFalse(manifest1.equals(this)); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + Manifest manifest1 = null; + Manifest manifest2 = new Manifest(); + InputStream is = null; + try { + manifest1 = new Manifest(new URL(Support_Resources + .getURL("manifest/hyts_MANIFEST.MF")).openStream()); + } catch (MalformedURLException e) { + fail("Malformed URL"); + } catch (IOException e) { + fail("IOException"); + } + assertEquals(manifest1.hashCode(), manifest1.hashCode()); + assertNotSame(manifest1.hashCode(), manifest2.hashCode()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "write", + args = {java.io.OutputStream.class} + ) + public void test_writeLjava_io_OutputStream() throws IOException { + byte b[] = null; + Manifest manifest1 = null; + Manifest manifest2 = null; + InputStream is = null; + try { + manifest1 = new Manifest(new URL(Support_Resources + .getURL("manifest/hyts_MANIFEST.MF")).openStream()); + } catch (MalformedURLException e) { + fail("Malformed URL"); + } + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + + manifest1.write(baos); + + b = baos.toByteArray(); + + File f = File.createTempFile("111", "111"); + FileOutputStream fos = new FileOutputStream(f); + fos.close(); + try { + manifest1.write(fos); + fail("IOException expected"); + } catch (IOException e) { + // expected + } + f.delete(); + + ByteArrayInputStream bais = new ByteArrayInputStream(b); + + try { + manifest2 = new Manifest(bais); + } catch (MalformedURLException e) { + fail("Malformed URL"); + } + + assertTrue(manifest1.equals(manifest2)); + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/Pack200PackerTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/Pack200PackerTest.java new file mode 100644 index 0000000..355fd27 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/Pack200PackerTest.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; + +import junit.framework.TestCase; + +import tests.support.resource.Support_Resources; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; +import java.util.jar.Pack200; +import java.util.jar.Pack200.Packer; + +@TestTargetClass(Pack200.Packer.class) +public class Pack200PackerTest extends TestCase { + Packer packer; + Map properties; + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "properties", + args = {} + ) + @KnownFailure("No Implementation in Android!") + public void testProperties() { + assertTrue(properties.size()>0); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "pack", + args = {java.util.jar.JarFile.class, java.io.OutputStream.class} + ) + @KnownFailure("No Implementation in Android!") + public void testPackJarFileOutputStream() throws IOException { + File resources = Support_Resources.createTempFolder(); + //Use junit4.jar file for testing pack200 compressing rate. + //file can be changed to any other. + Support_Resources.copyFile(resources, null, "junit4-4.3.1.jar"); + File jarFile = new File(resources, "junit4-4.3.1.jar"); + JarFile jf = new JarFile(jarFile); + + File packFile1 = Support_Resources.createTempFile("pack200_1"); + File packFile2 = Support_Resources.createTempFile("pack200_2"); + File packFile3 = Support_Resources.createTempFile("pack200_3"); + FileOutputStream fos1 = new FileOutputStream(packFile1); + FileOutputStream fos2 = new FileOutputStream(packFile2); + FileOutputStream fos3 = new FileOutputStream(packFile3); + properties.put(Packer.EFFORT, "0"); + packer.pack(jf, fos1); + jf.close(); + fos1.close(); + jf = new JarFile(jarFile); + properties.put(Packer.EFFORT, "1"); + packer.pack(jf, fos2); + jf.close(); + fos2.close(); + jf = new JarFile(jarFile); + properties.put(Packer.EFFORT, "9"); + packer.pack(jf, fos3); + jf.close(); + fos3.close(); + assertTrue(jarFile.length()!=packFile1.length()); + assertTrue(packFile1.length()>packFile2.length()); + assertTrue(packFile2.length()>packFile3.length()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "pack", + args = {java.util.jar.JarInputStream.class, java.io.OutputStream.class} + ) + @KnownFailure("No Implementation in Android!") + public void testPackJarInputStreamOutputStream() throws IOException { + File resources = Support_Resources.createTempFolder(); + //Use junit4.jar file for testing pack200 compressing rate. + //file can be changed to any other. + Support_Resources.copyFile(resources, null, "junit4-4.3.1.jar"); + File jarFile = new File(resources, "junit4-4.3.1.jar"); + JarInputStream jis = new JarInputStream(new FileInputStream(jarFile)); + + File packFile1 = Support_Resources.createTempFile("pack200_1"); + File packFile2 = Support_Resources.createTempFile("pack200_2"); + File packFile3 = Support_Resources.createTempFile("pack200_3"); + FileOutputStream fos1 = new FileOutputStream(packFile1); + FileOutputStream fos2 = new FileOutputStream(packFile2); + FileOutputStream fos3 = new FileOutputStream(packFile3); + properties.put(Packer.EFFORT, "0"); + packer.pack(jis, fos1); + fos1.close(); + jis = new JarInputStream(new FileInputStream(jarFile)); + properties.put(Packer.EFFORT, "1"); + packer.pack(jis, fos2); + fos2.close(); + jis = new JarInputStream(new FileInputStream(jarFile)); + properties.put(Packer.EFFORT, "9"); + packer.pack(jis, fos3); + fos3.close(); + assertTrue(jarFile.length()!=packFile1.length()); + assertTrue(packFile1.length()>packFile2.length()); + assertTrue(packFile2.length()>packFile3.length()); + } + +/* + * java.beans.* not implemented yet on android platform + * class MyPCL implements PropertyChangeListener { + boolean flag = false; + + public boolean isCalled() { + return flag; + } + + public void propertyChange(PropertyChangeEvent arg0) { + flag = true; + } + } +@TestInfo( + level = TestLevel.COMPLETE, + + targets = { + @TestTarget( + methodName = "addPropertyChangeListener", + methodArgs = {java.beans.PropertyChangeListener.class} + ) + }) + public void testAddPropertyChangeListener() { + MyPCL pcl = new MyPCL(); + packer.addPropertyChangeListener(pcl); + assertFalse(pcl.isCalled()); + properties.put(Packer.EFFORT, "7"); + assertTrue(pcl.isCalled()); + } + +@TestInfo( + level = TestLevel.COMPLETE, + + targets = { + @TestTarget( + methodName = "removePropertyChangeListener", + methodArgs = {java.beans.PropertyChangeListener.class} + ) + }) + public void testRemovePropertyChangeListener() { + MyPCL pcl = new MyPCL(); + packer.addPropertyChangeListener(pcl); + assertFalse(pcl.isCalled()); + packer.removePropertyChangeListener(pcl); + properties.put(Packer.EFFORT, "7"); + assertFalse(pcl.isCalled()); + } +*/ + @Override + protected void setUp() { + packer = Pack200.newPacker(); + properties = packer.properties(); + } + + @Override + protected void tearDown() { + packer = null; + properties = null; + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/Pack200Test.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/Pack200Test.java new file mode 100644 index 0000000..9fb3cf9 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/Pack200Test.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; + +import junit.framework.TestCase; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.jar.Pack200; + +@TestTargetClass(Pack200.class) +public class Pack200Test extends TestCase { + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "newPacker", + args = {} + ) + @KnownFailure("No Implementation in Android!") + public void testNewPacker() { + Method[] methodsInt = Pack200.Packer.class.getDeclaredMethods(); + Method[] methodsImpl = Pack200.newPacker().getClass() + .getDeclaredMethods(); + Field[] fieldsInt = Pack200.Packer.class.getFields(); + Field[] fieldsImpl = Pack200.newPacker().getClass().getFields(); + int i, k; + boolean flag; + for (i = 0; i < methodsInt.length; i++) { + flag = false; + for (k = 0; k < methodsImpl.length; k++) { + if (methodsInt[i].getName().equals(methodsImpl[k].getName())) { + flag = true; + break; + } + } + assertTrue("Not all methods were implemented", flag); + } + + for (i = 0; i < fieldsInt.length; i++) { + flag = false; + for (k = 0; k < fieldsImpl.length; k++) { + if (fieldsInt[i].getName().equals(fieldsImpl[k].getName())) { + flag = true; + break; + } + } + assertTrue("Not all fields were existed", flag); + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "newUnpacker", + args = {} + ) + @KnownFailure("No Implementation in Android!") + public void testNewUnpacker() { + assertNotNull(Pack200.newUnpacker().getClass()); + Method[] methodsInt = Pack200.Unpacker.class.getDeclaredMethods(); + Method[] methodsImpl = Pack200.newUnpacker().getClass() + .getDeclaredMethods(); + Field[] fieldsInt = Pack200.Unpacker.class.getFields(); + Field[] fieldsImpl = Pack200.newUnpacker().getClass().getFields(); + int i, k; + boolean flag; + for (i = 0; i < methodsInt.length; i++) { + flag = false; + for (k = 0; k < methodsImpl.length; k++) { + if (methodsInt[i].getName().equals(methodsImpl[k].getName())) { + flag = true; + break; + } + } + assertTrue("Not all methods were implemented", flag); + } + + for (i = 0; i < fieldsInt.length; i++) { + flag = false; + for (k = 0; k < fieldsImpl.length; k++) { + if (fieldsInt[i].getName().equals(fieldsImpl[k].getName())) { + flag = true; + break; + } + } + assertTrue("Not all fields were existed", flag); + } + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/Pack200UnpackerTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/Pack200UnpackerTest.java new file mode 100644 index 0000000..4d5ede3 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/Pack200UnpackerTest.java @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; + +import junit.framework.TestCase; + +import tests.support.resource.Support_Resources; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.Map; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; +import java.util.jar.Pack200; +import java.util.jar.Pack200.Packer; +import java.util.jar.Pack200.Unpacker; + +@TestTargetClass(Pack200.Unpacker.class) +public class Pack200UnpackerTest extends TestCase { + Unpacker unpacker; + Map properties; + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "properties", + args = {} + ) + @KnownFailure("No Implementation in Android!") + public void testProperties() { + assertTrue(properties.size()>0); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "unpack", + args = {java.io.File.class, java.util.jar.JarOutputStream.class} + ) + @KnownFailure("No Implementation in Android!") + public void testUnpackInputStreamJarOutputStream() throws IOException { + File resources = Support_Resources.createTempFolder(); + //Use junit4.jar file for testing pack200 compressing rate. + //file can be changed to any other. + Support_Resources.copyFile(resources, null, "junit4-4.3.1.jar"); + File jarFile = new File(resources, "junit4-4.3.1.jar"); + JarFile jf = new JarFile(jarFile); + int jarEntries = jf.size(); + + File packFile1 = Support_Resources.createTempFile("pack200_1"); + File packFile2 = Support_Resources.createTempFile("pack200_2"); + File packFile3 = Support_Resources.createTempFile("pack200_3"); + FileOutputStream fos1 = new FileOutputStream(packFile1); + FileOutputStream fos2 = new FileOutputStream(packFile2); + FileOutputStream fos3 = new FileOutputStream(packFile3); + properties.put(Packer.EFFORT, "0"); + Packer packer = Pack200.newPacker(); + packer.pack(jf, fos1); + jf.close(); + fos1.close(); + jf = new JarFile(jarFile); + properties.put(Packer.EFFORT, "1"); + packer.pack(jf, fos2); + jf.close(); + fos2.close(); + jf = new JarFile(jarFile); + properties.put(Packer.EFFORT, "9"); + packer.pack(jf, fos3); + jf.close(); + fos3.close(); + + File jarFile1 = Support_Resources.createTempFile("jar_1"); + File jarFile2 = Support_Resources.createTempFile("jar_2"); + File jarFile3 = Support_Resources.createTempFile("jar_3"); + JarOutputStream jos1 = new JarOutputStream(new FileOutputStream(jarFile1)); + JarOutputStream jos2 = new JarOutputStream(new FileOutputStream(jarFile2)); + JarOutputStream jos3 = new JarOutputStream(new FileOutputStream(jarFile3)); + + unpacker.unpack(packFile1, jos1); + unpacker.unpack(packFile2, jos2); + unpacker.unpack(packFile3, jos3); + + jos1.close(); + jos2.close(); + jos3.close(); + + assertEquals(jarFile1.length(), jarFile2.length()); + assertEquals(jarFile2.length(), jarFile3.length()); + + assertEquals(jarEntries, new JarFile(jarFile1).size()); + assertEquals(jarEntries, new JarFile(jarFile2).size()); + assertEquals(jarEntries, new JarFile(jarFile3).size()); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "unpack", + args = {java.io.InputStream.class, java.util.jar.JarOutputStream.class} + ) + @KnownFailure("No Implementation in Android!") + public void testUnpackFileJarOutputStream() throws IOException { + File resources = Support_Resources.createTempFolder(); + //Use junit4.jar file for testing pack200 compressing rate. + //file can be changed to any other. + Support_Resources.copyFile(resources, null, "junit4-4.3.1.jar"); + File jarFile = new File(resources, "junit4-4.3.1.jar"); + JarFile jf = new JarFile(jarFile); + int jarEntries = jf.size(); + + File packFile1 = Support_Resources.createTempFile("pack200_1"); + File packFile2 = Support_Resources.createTempFile("pack200_2"); + File packFile3 = Support_Resources.createTempFile("pack200_3"); + FileOutputStream fos1 = new FileOutputStream(packFile1); + FileOutputStream fos2 = new FileOutputStream(packFile2); + FileOutputStream fos3 = new FileOutputStream(packFile3); + properties.put(Packer.EFFORT, "0"); + Packer packer = Pack200.newPacker(); + packer.pack(jf, fos1); + jf.close(); + fos1.close(); + jf = new JarFile(jarFile); + properties.put(Packer.EFFORT, "1"); + packer.pack(jf, fos2); + jf.close(); + fos2.close(); + jf = new JarFile(jarFile); + properties.put(Packer.EFFORT, "9"); + packer.pack(jf, fos3); + jf.close(); + fos3.close(); + + File jarFile1 = Support_Resources.createTempFile("jar_1"); + File jarFile2 = Support_Resources.createTempFile("jar_2"); + File jarFile3 = Support_Resources.createTempFile("jar_3"); + JarOutputStream jos1 = new JarOutputStream(new FileOutputStream(jarFile1)); + JarOutputStream jos2 = new JarOutputStream(new FileOutputStream(jarFile2)); + JarOutputStream jos3 = new JarOutputStream(new FileOutputStream(jarFile3)); + FileInputStream fis1 = new FileInputStream(packFile1); + FileInputStream fis2 = new FileInputStream(packFile2); + FileInputStream fis3 = new FileInputStream(packFile3); + + unpacker.unpack(fis1, jos1); + unpacker.unpack(fis2, jos2); + unpacker.unpack(fis3, jos3); + + jos1.close(); + jos2.close(); + jos3.close(); + + assertEquals(jarFile1.length(), jarFile2.length()); + assertEquals(jarFile2.length(), jarFile3.length()); + + assertEquals(jarEntries, new JarFile(jarFile1).size()); + assertEquals(jarEntries, new JarFile(jarFile2).size()); + assertEquals(jarEntries, new JarFile(jarFile3).size()); + } +/* + * java.beans.* not implemented yet on android platform + * class MyPCL implements PropertyChangeListener { + boolean flag = false; + + public boolean isCalled() { + return flag; + } + + public void propertyChange(PropertyChangeEvent arg0) { + flag = true; + } + } + +@TestInfo( + level = TestLevel.COMPLETE, + + targets = { + @TestTarget( + methodName = "addPropertyChangeListener", + methodArgs = {java.beans.PropertyChangeListener.class} + ) + }) + public void testAddPropertyChangeListener() { + MyPCL pcl = new MyPCL(); + unpacker.addPropertyChangeListener(pcl); + assertFalse(pcl.isCalled()); + properties.put(Unpacker.PROGRESS, "0"); + assertTrue(pcl.isCalled()); + } + +@TestInfo( + level = TestLevel.COMPLETE, + + targets = { + @TestTarget( + methodName = "removePropertyChangeListener", + methodArgs = {java.beans.PropertyChangeListener.class} + ) + }) + public void testRemovePropertyChangeListener() { + MyPCL pcl = new MyPCL(); + unpacker.addPropertyChangeListener(pcl); + assertFalse(pcl.isCalled()); + unpacker.removePropertyChangeListener(pcl); + properties.put(Unpacker.PROGRESS, "7"); + assertFalse(pcl.isCalled()); + } +*/ + @Override + protected void setUp() { + unpacker = Pack200.newUnpacker(); + properties = unpacker.properties(); + } + + @Override + protected void tearDown() { + unpacker = null; + properties = null; + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ZipExecTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ZipExecTest.java new file mode 100644 index 0000000..c6f07de --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ZipExecTest.java @@ -0,0 +1,324 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.jar; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import tests.support.Support_Exec; +import tests.support.resource.Support_Resources; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; + +/** + * + * tests for various cases of java -jar ... execution with .zip files as args + * some tests are just copy of JarExecTest ones + */ + +@TestTargetClass(ZipOutputStream.class) +public class ZipExecTest extends junit.framework.TestCase { + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Regression functional test. Exception checking missed.", + method = "putNextEntry", + args = {java.util.zip.ZipEntry.class} + ) + @KnownFailure("Maybe not a failure, but dalvikvm -jar is not supported (, as yet).") + public void test_1562() throws Exception { + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + + File outputZip = File.createTempFile("hyts_", ".zip"); + outputZip.deleteOnExit(); + ZipOutputStream zout = new ZipOutputStream(new FileOutputStream( + outputZip)); + File resources = Support_Resources.createTempFolder(); + + for (String zipClass : new String[] {"Foo", "Bar"}) { + zout.putNextEntry(new ZipEntry("foo/bar/execjartest/" + zipClass + + ".class")); + zout.write(getResource(resources, "hyts_" + zipClass + ".ser")); + } + + zout.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + man.write(zout); + zout.close(); + + + // set up the VM parameters + String[] args = new String[] {"-jar", outputZip.getAbsolutePath()}; + + // execute the JAR and read the result + String res = Support_Exec.execJava(args, null, false); + + assertTrue("Error executing ZIP : result returned was incorrect.", res + .startsWith("FOOBAR")); + } + + /** + * tests Class-Path entry in manifest + * + * @throws Exception in case of troubles + */ + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Functional test.", + method = "ZipOutputStream", + args = {java.io.OutputStream.class} + ) + @KnownFailure("Maybe not a failure, but dalvikvm -jar is not supported (, as yet).") + public void test_zip_class_path() throws Exception { + File fooZip = File.createTempFile("hyts_", ".zip"); + File barZip = File.createTempFile("hyts_", ".zip"); + fooZip.deleteOnExit(); + barZip.deleteOnExit(); + + // create the manifest + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + att.put(Attributes.Name.CLASS_PATH, barZip.getName()); + + File resources = Support_Resources.createTempFolder(); + + ZipOutputStream zoutFoo = new ZipOutputStream(new FileOutputStream( + fooZip)); + zoutFoo.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + man.write(zoutFoo); + zoutFoo.putNextEntry(new ZipEntry("foo/bar/execjartest/Foo.class")); + zoutFoo.write(getResource(resources, "hyts_Foo.ser")); + zoutFoo.close(); + + ZipOutputStream zoutBar = new ZipOutputStream(new FileOutputStream( + barZip)); + zoutBar.putNextEntry(new ZipEntry("foo/bar/execjartest/Bar.class")); + zoutBar.write(getResource(resources, "hyts_Bar.ser")); + zoutBar.close(); + + String[] args = new String[] {"-jar", fooZip.getAbsolutePath()}; + + // execute the JAR and read the result + String res = Support_Exec.execJava(args, null, false); + + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + + // rewrite manifest so it contains not only reference to bar but useless + // entries as well + att.put(Attributes.Name.CLASS_PATH, "xx yy zz " + barZip.getName()); + zoutFoo = new ZipOutputStream(new FileOutputStream(fooZip)); + zoutFoo.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + man.write(zoutFoo); + zoutFoo.putNextEntry(new ZipEntry("foo/bar/execjartest/Foo.class")); + zoutFoo.write(getResource(resources, "hyts_Foo.ser")); + zoutFoo.close(); + // execute the JAR and read the result + res = Support_Exec.execJava(args, null, false); + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + + + // play with relative file names - put relative path as ../<parent dir + // name>/xx.zip + att.put(Attributes.Name.CLASS_PATH, ".." + File.separator + + barZip.getParentFile().getName() + File.separator + + barZip.getName()); + zoutFoo = new ZipOutputStream(new FileOutputStream(fooZip)); + zoutFoo.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + man.write(zoutFoo); + zoutFoo.putNextEntry(new ZipEntry("foo/bar/execjartest/Foo.class")); + zoutFoo.write(getResource(resources, "hyts_Foo.ser")); + zoutFoo.close(); + // execute the ZIP and read the result + res = Support_Exec.execJava(args, null, false); + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + } + + + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Functional test.", + method = "ZipOutputStream", + args = {java.io.OutputStream.class} + ) + @KnownFailure("Maybe not a failure, but dalvikvm -jar is not supported (, as yet).") + public void test_zip_jar_mix() throws Exception { + File fooJar = File.createTempFile("hyts_", ".jar"); + File barZip = File.createTempFile("hyts_", ".zip"); + fooJar.deleteOnExit(); + barZip.deleteOnExit(); + + // create the manifest + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + att.put(Attributes.Name.CLASS_PATH, barZip.getName()); + + File resources = Support_Resources.createTempFolder(); + + JarOutputStream joutFoo = new JarOutputStream(new FileOutputStream( + fooJar), man); + joutFoo.putNextEntry(new JarEntry("foo/bar/execjartest/Foo.class")); + joutFoo.write(getResource(resources, "hyts_Foo.ser")); + joutFoo.close(); + + ZipOutputStream zoutBar = new ZipOutputStream(new FileOutputStream( + barZip)); + zoutBar.putNextEntry(new ZipEntry("foo/bar/execjartest/Bar.class")); + zoutBar.write(getResource(resources, "hyts_Bar.ser")); + zoutBar.close(); + + String[] args = new String[] {"-jar", fooJar.getAbsolutePath()}; + + // execute the JAR and read the result + String res = Support_Exec.execJava(args, null, false); + + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + } + + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Functional test.", + method = "ZipOutputStream", + args = {java.io.OutputStream.class} + ) + @KnownFailure("Maybe not a failure, but dalvikvm -jar is not supported (, as yet).") + public void test_zip_jar_mix_1() throws Exception { + File fooZip = File.createTempFile("hyts_", ".zip"); + File barJar = File.createTempFile("hyts_", ".jar"); + fooZip.deleteOnExit(); + barJar.deleteOnExit(); + + // create the manifest + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + att.put(Attributes.Name.CLASS_PATH, barJar.getName()); + + File resources = Support_Resources.createTempFolder(); + + ZipOutputStream zoutFoo = new ZipOutputStream(new FileOutputStream( + fooZip)); + zoutFoo.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + man.write(zoutFoo); + zoutFoo.putNextEntry(new ZipEntry("foo/bar/execjartest/Foo.class")); + zoutFoo.write(getResource(resources, "hyts_Foo.ser")); + zoutFoo.close(); + + JarOutputStream joutBar = new JarOutputStream(new FileOutputStream( + barJar)); + joutBar.putNextEntry(new ZipEntry("foo/bar/execjartest/Bar.class")); + joutBar.write(getResource(resources, "hyts_Bar.ser")); + joutBar.close(); + + String[] args = new String[] {"-jar", fooZip.getAbsolutePath()}; + + // execute the JAR and read the result + String res = Support_Exec.execJava(args, null, false); + + assertTrue("Error executing ZIP : result returned was incorrect.", res + .startsWith("FOOBAR")); + } + + /** + * tests case when Main-Class is not in the zip launched but in another zip + * referenced by Class-Path + * + * @throws Exception in case of troubles + */ + @TestTargetNew( + level = TestLevel.ADDITIONAL, + notes = "Functional test.", + method = "ZipOutputStream", + args = {java.io.OutputStream.class} + ) + @KnownFailure("Maybe not a failure, but dalvikvm -jar is not supported (, as yet).") + public void test_main_class_in_another_zip() throws Exception { + File fooZip = File.createTempFile("hyts_", ".zip"); + File barZip = File.createTempFile("hyts_", ".zip"); + fooZip.deleteOnExit(); + barZip.deleteOnExit(); + + // create the manifest + Manifest man = new Manifest(); + Attributes att = man.getMainAttributes(); + att.put(Attributes.Name.MANIFEST_VERSION, "1.0"); + att.put(Attributes.Name.MAIN_CLASS, "foo.bar.execjartest.Foo"); + att.put(Attributes.Name.CLASS_PATH, fooZip.getName()); + + File resources = Support_Resources.createTempFolder(); + + ZipOutputStream zoutFoo = new ZipOutputStream(new FileOutputStream( + fooZip)); + zoutFoo.putNextEntry(new ZipEntry("foo/bar/execjartest/Foo.class")); + zoutFoo.write(getResource(resources, "hyts_Foo.ser")); + zoutFoo.close(); + + ZipOutputStream zoutBar = new ZipOutputStream(new FileOutputStream( + barZip)); + zoutBar.putNextEntry(new ZipEntry("META-INF/MANIFEST.MF")); + man.write(zoutBar); + + zoutBar.putNextEntry(new ZipEntry("foo/bar/execjartest/Bar.class")); + zoutBar.write(getResource(resources, "hyts_Bar.ser")); + zoutBar.close(); + + String[] args = new String[] {"-jar", barZip.getAbsolutePath()}; + + // execute the JAR and read the result + String res = Support_Exec.execJava(args, null, false); + + assertTrue("Error executing JAR : result returned was incorrect.", res + .startsWith("FOOBAR")); + } + + + private static byte[] getResource(File tempDir, String resourceName) + throws IOException { + Support_Resources.copyFile(tempDir, null, resourceName); + File resourceFile = new File(tempDir, resourceName); + resourceFile.deleteOnExit(); + + // read whole resource data into memory + byte[] resourceBody = new byte[(int) resourceFile.length()]; + FileInputStream fis = new FileInputStream(resourceFile); + fis.read(resourceBody); + fis.close(); + + return resourceBody; + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/Adler32Test.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/Adler32Test.java new file mode 100644 index 0000000..532a3a6 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/Adler32Test.java @@ -0,0 +1,224 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.util.zip.Adler32; + +@TestTargetClass(Adler32.class) +public class Adler32Test extends junit.framework.TestCase { + + /** + * @tests java.util.zip.Adler32#Adler32() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Adler32", + args = {} + ) + public void test_Constructor() { + // test method of java.util.zip.Adler32() + Adler32 adl = new Adler32(); + assertEquals("Constructor of adl32 failed", 1, adl.getValue()); + } + + /** + * @tests java.util.zip.Adler32#getValue() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getValue", + args = {} + ) + public void test_getValue() { + // test methods of java.util.zip.getValue() + Adler32 adl = new Adler32(); + assertEquals( + "GetValue should return a zero as a result of construction an object of Adler32", + 1, adl.getValue()); + + adl.reset(); + adl.update(1); + // System.out.print("value of adl"+adl.getValue()); + // The value of the adl should be 131074 + assertEquals( + "update(int) failed to update the checksum to the correct value ", + 131074, adl.getValue()); + adl.reset(); + assertEquals("reset failed to reset the checksum value to zero", 1, adl + .getValue()); + + adl.reset(); + adl.update(Integer.MIN_VALUE); + // System.out.print("value of adl " + adl.getValue()); + // The value of the adl should be 65537 + assertEquals( + "update(min) failed to update the checksum to the correct value ", + 65537L, adl.getValue()); + } + + /** + * @tests java.util.zip.Adler32#reset() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "reset", + args = {} + ) + public void test_reset() { + // test methods of java.util.zip.reset() + Adler32 adl = new Adler32(); + adl.update(1); + // System.out.print("value of adl"+adl.getValue()); + // The value of the adl should be 131074 + assertEquals( + "update(int) failed to update the checksum to the correct value ", + 131074, adl.getValue()); + adl.reset(); + assertEquals("reset failed to reset the checksum value to zero", 1, adl + .getValue()); + } + + /** + * @tests java.util.zip.Adler32#update(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "update", + args = {int.class} + ) + public void test_updateI() { + // test methods of java.util.zip.update(int) + Adler32 adl = new Adler32(); + adl.update(1); + // The value of the adl should be 131074 + assertEquals( + "update(int) failed to update the checksum to the correct value ", + 131074, adl.getValue()); + + adl.reset(); + adl.update(Integer.MAX_VALUE); + // System.out.print("value of adl " + adl.getValue()); + // The value of the adl should be 16777472 + assertEquals( + "update(max) failed to update the checksum to the correct value ", + 16777472L, adl.getValue()); + + adl.reset(); + adl.update(Integer.MIN_VALUE); + // System.out.print("value of adl " + adl.getValue()); + // The value of the adl should be 65537 + assertEquals( + "update(min) failed to update the checksum to the correct value ", + 65537L, adl.getValue()); + + } + + /** + * @tests java.util.zip.Adler32#update(byte[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "update", + args = {byte[].class} + ) + public void test_update$B() { + // test method of java.util.zip.update(byte[]) + byte byteArray[] = {1, 2}; + Adler32 adl = new Adler32(); + adl.update(byteArray); + // System.out.print("value of adl"+adl.getValue()); + // The value of the adl should be 393220 + assertEquals( + "update(byte[]) failed to update the checksum to the correct value ", + 393220, adl.getValue()); + + adl.reset(); + byte byteEmpty[] = new byte[10000]; + adl.update(byteEmpty); + // System.out.print("value of adl"+adl.getValue()); + // The value of the adl should be 655360001 + assertEquals( + "update(byte[]) failed to update the checksum to the correct value ", + 655360001L, adl.getValue()); + + } + + /** + * @tests java.util.zip.Adler32#update(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "update", + args = {byte[].class, int.class, int.class} + ) + public void test_update$BII() { + // test methods of java.util.zip.update(byte[],int,int) + byte[] byteArray = {1, 2, 3}; + Adler32 adl = new Adler32(); + int off = 2;// accessing the 2nd element of byteArray + int len = 1; + int lenError = 3; + int offError = 4; + adl.update(byteArray, off, len); + // System.out.print("value of adl"+adl.getValue()); + // The value of the adl should be 262148 + assertEquals( + "update(byte[],int,int) failed to update the checksum to the correct value ", + 262148, adl.getValue()); + int r = 0; + + try { + adl.update(byteArray, off, lenError); + } catch (ArrayIndexOutOfBoundsException e) { + r = 1; + } + assertEquals( + "update(byte[],int,int) failed b/c lenError>byte[].length-off", + 1, r); + + try { + adl.update(byteArray, offError, len); + } catch (ArrayIndexOutOfBoundsException e) { + r = 2; + } + assertEquals( + "update(byte[],int,int) failed b/c offError>byte[].length", 2, + r); + + } + + @Override + protected void setUp() { + } + + @Override + protected void tearDown() { + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/AllTests.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/AllTests.java new file mode 100644 index 0000000..acde889 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/AllTests.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import junit.framework.Test; +import junit.framework.TestSuite; + +/** + * Test suite for java.util.zip package. + */ +public class AllTests { + + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + public static Test suite() { + TestSuite suite = tests.TestSuiteFactory.createTestSuite( + "Suite org.apache.harmony.archive.tests.java.util.zip"); + // $JUnit-BEGIN$ + suite.addTestSuite(Adler32Test.class); + suite.addTestSuite(CheckedInputStreamTest.class); + suite.addTestSuite(CheckedOutputStreamTest.class); + suite.addTestSuite(CRC32Test.class); + suite.addTestSuite(DataFormatExceptionTest.class); + suite.addTestSuite(DeflaterOutputStreamTest.class); + suite.addTestSuite(DeflaterTest.class); + suite.addTestSuite(GZIPInputStreamTest.class); + suite.addTestSuite(GZIPOutputStreamTest.class); + suite.addTestSuite(InflaterInputStreamTest.class); + suite.addTestSuite(InflaterTest.class); + suite.addTestSuite(ZipEntryTest.class); + suite.addTestSuite(ZipExceptionTest.class); + suite.addTestSuite(ZipFileTest.class); + suite.addTestSuite(ZipInputStreamTest.class); + suite.addTestSuite(ZipOutputStreamTest.class); + // $JUnit-END$ + return suite; + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/CRC32Test.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/CRC32Test.java new file mode 100644 index 0000000..805cab3 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/CRC32Test.java @@ -0,0 +1,242 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.util.zip.CRC32; + +@TestTargetClass(CRC32.class) +public class CRC32Test extends junit.framework.TestCase { + + /** + * @tests java.util.zip.CRC32#CRC32() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "CRC32", + args = {} + ) + public void test_Constructor() { + // test methods of java.util.zip.CRC32() + CRC32 crc = new CRC32(); + assertEquals("Constructor of CRC32 failed", 0, crc.getValue()); + } + + /** + * @tests java.util.zip.CRC32#getValue() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getValue", + args = {} + ) + public void test_getValue() { + // test methods of java.util.zip.crc32.getValue() + CRC32 crc = new CRC32(); + assertEquals( + "getValue() should return a zero as a result of constructing a CRC32 instance", + 0, crc.getValue()); + + crc.reset(); + crc.update(Integer.MAX_VALUE); + // System.out.print("value of crc " + crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 4278190080 + assertEquals( + "update(max) failed to update the checksum to the correct value ", + 4278190080L, crc.getValue()); + + crc.reset(); + byte byteEmpty[] = new byte[10000]; + crc.update(byteEmpty); + // System.out.print("value of crc"+crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 1295764014 + assertEquals( + "update(byte[]) failed to update the checksum to the correct value ", + 1295764014L, crc.getValue()); + + crc.reset(); + crc.update(1); + // System.out.print("value of crc"+crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 2768625435 + // assertEquals("update(int) failed to update the checksum to the + // correct + // value ",2768625435L, crc.getValue()); + crc.reset(); + assertEquals("reset failed to reset the checksum value to zero", 0, crc + .getValue()); + } + + /** + * @tests java.util.zip.CRC32#reset() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "reset", + args = {} + ) + public void test_reset() { + // test methods of java.util.zip.crc32.reset() + CRC32 crc = new CRC32(); + crc.update(1); + // System.out.print("value of crc"+crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 2768625435 + assertEquals( + "update(int) failed to update the checksum to the correct value ", + 2768625435L, crc.getValue()); + crc.reset(); + assertEquals("reset failed to reset the checksum value to zero", 0, crc + .getValue()); + + } + + /** + * @tests java.util.zip.CRC32#update(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "update", + args = {int.class} + ) + public void test_updateI() { + // test methods of java.util.zip.crc32.update(int) + CRC32 crc = new CRC32(); + crc.update(1); + // System.out.print("value of crc"+crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 2768625435 + assertEquals( + "update(1) failed to update the checksum to the correct value ", + 2768625435L, crc.getValue()); + + crc.reset(); + crc.update(Integer.MAX_VALUE); + // System.out.print("value of crc " + crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 4278190080 + assertEquals( + "update(max) failed to update the checksum to the correct value ", + 4278190080L, crc.getValue()); + + crc.reset(); + crc.update(Integer.MIN_VALUE); + // System.out.print("value of crc " + crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 3523407757 + assertEquals( + "update(min) failed to update the checksum to the correct value ", + 3523407757L, crc.getValue()); + } + + /** + * @tests java.util.zip.CRC32#update(byte[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "update", + args = {byte[].class} + ) + public void test_update$B() { + // test methods of java.util.zip.crc32.update(byte[]) + byte byteArray[] = {1, 2}; + CRC32 crc = new CRC32(); + crc.update(byteArray); + // System.out.print("value of crc"+crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 3066839698 + assertEquals( + "update(byte[]) failed to update the checksum to the correct value ", + 3066839698L, crc.getValue()); + + crc.reset(); + byte byteEmpty[] = new byte[10000]; + crc.update(byteEmpty); + // System.out.print("value of crc"+crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 1295764014 + assertEquals( + "update(byte[]) failed to update the checksum to the correct value ", + 1295764014L, crc.getValue()); + } + + /** + * @tests java.util.zip.CRC32#update(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "update", + args = {byte[].class, int.class, int.class} + ) + public void test_update$BII() { + // test methods of java.util.zip.update(byte[],int,int) + byte[] byteArray = {1, 2, 3}; + CRC32 crc = new CRC32(); + int off = 2;// accessing the 2nd element of byteArray + int len = 1; + int lenError = 3; + int offError = 4; + crc.update(byteArray, off, len); + // System.out.print("value of crc"+crc.getValue()); + // Ran JDK and discovered that the value of the CRC should be + // 1259060791 + assertEquals( + "update(byte[],int,int) failed to update the checksum to the correct value ", + 1259060791L, crc.getValue()); + int r = 0; + try { + crc.update(byteArray, off, lenError); + } catch (ArrayIndexOutOfBoundsException e) { + r = 1; + } + assertEquals( + "update(byte[],int,int) failed b/c lenError>byte[].length-off", + 1, r); + + try { + crc.update(byteArray, offError, len); + } catch (ArrayIndexOutOfBoundsException e) { + r = 2; + } + assertEquals( + "update(byte[],int,int) failed b/c offError>byte[].length", 2, + r); + } + + @Override + protected void setUp() { + + } + + @Override + protected void tearDown() { + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/CheckedInputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/CheckedInputStreamTest.java new file mode 100644 index 0000000..3b5cdb4 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/CheckedInputStreamTest.java @@ -0,0 +1,202 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.CRC32; +import java.util.zip.CheckedInputStream; +import junit.framework.TestCase; +import tests.support.resource.Support_Resources; + + +@TestTargetClass(CheckedInputStream.class) +public class CheckedInputStreamTest extends TestCase { + + @Override + protected void tearDown() { + try { + File deletedFile = new File("empty.txt"); + deletedFile.delete(); + } catch (SecurityException e) { + fail("Cannot delete file for security reasons"); + } + + } + + /** + * @tests java.util.zip.CheckedInputStream#CheckedInputStream(java.io.InputStream, + * java.util.zip.Checksum) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "CheckedInputStream", + args = {java.io.InputStream.class, java.util.zip.Checksum.class} + ) + public void test_ConstructorLjava_io_InputStreamLjava_util_zip_Checksum() + throws Exception { + InputStream checkInput = Support_Resources + .getStream("hyts_checkInput.txt"); + CheckedInputStream checkIn = new CheckedInputStream(checkInput, + new CRC32()); + assertEquals("constructor of checkedInputStream has failed", 0, checkIn + .getChecksum().getValue()); + checkInput.close(); + } + + /** + * @tests java.util.zip.CheckedInputStream#getChecksum() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getChecksum", + args = {} + ) + public void test_getChecksum() throws Exception { + byte outBuf[] = new byte[100]; + // testing getChecksum for an empty file + FileOutputStream outEmp = new FileOutputStream("empty.txt"); + outEmp.close(); + InputStream inEmp = new FileInputStream("empty.txt"); + CheckedInputStream checkEmpty = new CheckedInputStream(inEmp, + new CRC32()); + while (checkEmpty.read() >= 0) { + } + assertEquals("the checkSum value of an empty file is not zero", 0, + checkEmpty.getChecksum().getValue()); + inEmp.close(); + + // testing getChecksum for the file checkInput + InputStream checkInput = Support_Resources + .getStream("hyts_checkInput.txt"); + CheckedInputStream checkIn = new CheckedInputStream(checkInput, + new CRC32()); + while (checkIn.read() >= 0) { + } + // ran JDK and found that the checkSum value of this is 2036203193 + // System.out.print(" " + checkIn.getChecksum().getValue()); + assertEquals("the checksum value is incorrect", 2036203193, checkIn + .getChecksum().getValue()); + checkInput.close(); + // testing getChecksum for file checkInput + checkInput = Support_Resources.getStream("hyts_checkInput.txt"); + CheckedInputStream checkIn2 = new CheckedInputStream(checkInput, + new CRC32()); + checkIn2.read(outBuf, 0, 10); + // ran JDK and found that the checkSum value of this is 2235765342 + // System.out.print(" " + checkIn2.getChecksum().getValue()); + assertEquals("the checksum value is incorrect", 2235765342L, checkIn2 + .getChecksum().getValue()); + checkInput.close(); + } + + /** + * @tests java.util.zip.CheckedInputStream#skip(long) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "skip", + args = {long.class} + ) + public void test_skipJ() throws Exception { + // testing that the return by skip is valid + InputStream checkInput = Support_Resources + .getStream("hyts_checkInput.txt"); + CheckedInputStream checkIn = new CheckedInputStream(checkInput, + new CRC32()); + long skipValue = 5; + assertEquals( + "the value returned by skip(n) is not the same as its parameter", + skipValue, checkIn.skip(skipValue)); + checkIn.skip(skipValue); + // ran JDK and found the checkSum value is 2235765342 + // System.out.print(checkIn.getChecksum().getValue()); + assertEquals("checkSum value is not correct", 2235765342L, checkIn + .getChecksum().getValue()); + checkInput.close(); + try { + checkInput.skip(33); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + /** + * @tests java.util.zip.CheckedInputStream#read() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "read", + args = {} + ) + public void test_read() throws Exception { + // testing that the return by skip is valid + InputStream checkInput = Support_Resources + .getStream("hyts_checkInput.txt"); + CheckedInputStream checkIn = new CheckedInputStream(checkInput, + new CRC32()); + checkIn.read(); + checkIn.close(); + try { + checkIn.read(); + fail("IOException expected."); + } catch (IOException ee) { + // expected + } + long skipValue = 5; + checkInput.close(); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "read", + args = {byte[].class, int.class, int.class} + ) + public void test_read$byteII() throws Exception { + // testing that the return by skip is valid + InputStream checkInput = Support_Resources + .getStream("hyts_checkInput.txt"); + CheckedInputStream checkIn = new CheckedInputStream(checkInput, + new CRC32()); + byte buff[] = new byte[50]; + checkIn.read(buff, 10, 5); + checkIn.close(); + try { + checkIn.read(buff, 10, 5); + fail("IOException expected."); + } catch (IOException ee) { + // expected + } + long skipValue = 5; + checkInput.close(); + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/CheckedOutputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/CheckedOutputStreamTest.java new file mode 100644 index 0000000..c0a83d2 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/CheckedOutputStreamTest.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.Adler32; +import java.util.zip.CRC32; +import java.util.zip.CheckedOutputStream; + +@TestTargetClass(CheckedOutputStream.class) +public class CheckedOutputStreamTest extends junit.framework.TestCase { + + /** + * @tests java.util.zip.CheckedOutputStream#CheckedOutputStream(java.io.OutputStream, + * java.util.zip.Checksum) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "CheckedOutputStream", + args = {java.io.OutputStream.class, java.util.zip.Checksum.class} + ) + public void test_ConstructorLjava_io_OutputStreamLjava_util_zip_Checksum() { + // test method java.util.zip.checkedOutputStream.constructor + try { + FileOutputStream outFile = new FileOutputStream("chkOut.txt"); + CheckedOutputStream chkOut = new CheckedOutputStream(outFile, + new CRC32()); + assertEquals("the checkSum value of the constructor is not 0", 0, + chkOut.getChecksum().getValue()); + outFile.close(); + } catch (IOException e) { + fail("Unable to find file"); + } catch (SecurityException e) { + fail("file cannot be opened for writing due to security reasons"); + } + } + + /** + * @tests java.util.zip.CheckedOutputStream#getChecksum() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getChecksum", + args = {} + ) + public void test_getChecksum() { + // test method java.util.zip.checkedOutputStream.getChecksum() + byte byteArray[] = {1, 2, 3, 'e', 'r', 't', 'g', 3, 6}; + try { + FileOutputStream outFile = new FileOutputStream("chkOut.txt"); + CheckedOutputStream chkOut = new CheckedOutputStream(outFile, + new Adler32()); + chkOut.write(byteArray[4]); + // ran JDK and found that checkSum value is 7536755 + // System.out.print(chkOut.getChecksum().getValue()); + + assertEquals("the checkSum value for writeI is incorrect", 7536755, + chkOut.getChecksum().getValue()); + chkOut.getChecksum().reset(); + chkOut.write(byteArray, 5, 4); + // ran JDK and found that checkSum value is 51708133 + // System.out.print(" " +chkOut.getChecksum().getValue()); + + assertEquals("the checkSum value for writeBII is incorrect ", + 51708133, chkOut.getChecksum().getValue()); + outFile.close(); + } catch (IOException e) { + fail("Unable to find file"); + } catch (SecurityException e) { + fail("file cannot be opened for writing due to security reasons"); + } + } + + /** + * @tests java.util.zip.CheckedOutputStream#write(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "write", + args = {int.class} + ) + public void test_writeI() { + // test method java.util.zip.checkedOutputStream.writeI() + CheckedOutputStream chkOut = null; + byte byteArray[] = {1, 2, 3, 'e', 'r', 't', 'g', 3, 6}; + try { + FileOutputStream outFile = new FileOutputStream("chkOut.txt"); + chkOut = new CheckedOutputStream(outFile, new CRC32()); + for (byte element : byteArray) { + chkOut.write(element); + } + assertTrue( + "the checkSum value is zero, no bytes are written to the output file", + chkOut.getChecksum().getValue() != 0); + outFile.close(); + } catch (IOException e) { + fail("Unable to find file"); + } catch (SecurityException e) { + fail("File cannot be opened for writing due to security reasons"); + } + try { + chkOut.write(0); + fail("IOException expected"); + } catch (IOException e) { + // expected. + } + } + + /** + * @tests java.util.zip.CheckedOutputStream#write(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "write", + args = {byte[].class, int.class, int.class} + ) + public void test_write$BII() { + // test method java.util.zip.checkOutputStream.writeBII() + CheckedOutputStream chkOut = null; + byte byteArray[] = {1, 2, 3, 'e', 'r', 't', 'g', 3, 6}; + try { + FileOutputStream outFile = new FileOutputStream("chkOut.txt"); + chkOut = new CheckedOutputStream(outFile, new CRC32()); + chkOut.write(byteArray, 4, 5); + assertTrue( + "the checkSum value is zero, no bytes are written to the output file", + chkOut.getChecksum().getValue() != 0); + int r = 0; + try { + chkOut.write(byteArray, 4, 6); + } catch (IndexOutOfBoundsException e) { + r = 1; + } + assertEquals("boundary check is not performed", 1, r); + outFile.close(); + } catch (IOException e) { + fail("Unable to find file"); + } catch (SecurityException e) { + fail("file cannot be opened for writing due to security reasons"); + } catch (IndexOutOfBoundsException e) { + fail("Index for write is out of bounds"); + } + try { + chkOut.write(byteArray, 4, 5); + fail("IOException expected"); + } catch (IOException e) { + // expected + } + } + + @Override + protected void setUp() { + } + + @Override + protected void tearDown() { + try { + File deletedFile = new File("chkOut.txt"); + deletedFile.delete(); + } catch (SecurityException e) { + fail("Cannot delete file for security reasons"); + } + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/DataFormatExceptionTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/DataFormatExceptionTest.java new file mode 100644 index 0000000..6561bbd --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/DataFormatExceptionTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.util.zip.DataFormatException; + +@TestTargetClass(DataFormatException.class) +public class DataFormatExceptionTest extends TestCase { + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DataFormatException", + args = {} + ) + public void testDataFormatException() { + DataFormatException dfe = new DataFormatException(); + assertEquals(dfe.getMessage(), null); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DataFormatException", + args = {java.lang.String.class} + ) + public void testDataFormatExceptionString() { + DataFormatException dfe = new DataFormatException("Test"); + assertEquals(dfe.getMessage(), "Test"); + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/DeflaterOutputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/DeflaterOutputStreamTest.java new file mode 100644 index 0000000..9a804c1 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/DeflaterOutputStreamTest.java @@ -0,0 +1,461 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.Deflater; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.InflaterInputStream; + +import junit.framework.TestCase; + +@TestTargetClass(DeflaterOutputStream.class) +public class DeflaterOutputStreamTest extends TestCase { + + private class MyDeflaterOutputStream extends DeflaterOutputStream { + boolean deflateFlag = false; + + MyDeflaterOutputStream(OutputStream out) { + super(out); + } + + MyDeflaterOutputStream(OutputStream out, Deflater defl) { + super(out, defl); + } + + MyDeflaterOutputStream(OutputStream out, Deflater defl, int size) { + super(out, defl, size); + } + + byte[] getProtectedBuf() { + return buf; + } + + protected void deflate() throws IOException { + deflateFlag = true; + super.deflate(); + } + + boolean getDaflateFlag() { + return deflateFlag; + } + + void cleanDaflateFlag() { + deflateFlag = false; + } + } + + private byte outPutBuf[] = new byte[500]; + + @Override + protected void setUp() { + // setting up a deflater to be used + byte byteArray[] = {1, 3, 4, 7, 8}; + int x = 0; + Deflater deflate = new Deflater(1); + deflate.setInput(byteArray); + while (!(deflate.needsInput())) { + x += deflate.deflate(outPutBuf, x, outPutBuf.length - x); + } + deflate.finish(); + while (!(deflate.finished())) { + x = x + deflate.deflate(outPutBuf, x, outPutBuf.length - x); + } + deflate.end(); + } + + /** + * @tests java.util.zip.DeflaterOutputStream#DeflaterOutputStream(java.io.OutputStream, + * java.util.zip.Deflater) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DeflaterOutputStream", + args = {java.io.OutputStream.class, java.util.zip.Deflater.class} + ) + public void test_ConstructorLjava_io_OutputStreamLjava_util_zip_Deflater() + throws Exception { + byte byteArray[] = {1, 3, 4, 7, 8}; + File f1 = new File("hyts_Constru_OD.tst"); + FileOutputStream fos = new FileOutputStream(f1); + Deflater defl = null; + MyDeflaterOutputStream dos; + // Test for a null Deflater. + try { + dos = new MyDeflaterOutputStream(fos, defl); + fail("NullPointerException Not Thrown"); + } catch (NullPointerException e) { + } + defl = new Deflater(); + dos = new MyDeflaterOutputStream(fos, defl); + + // Test to see if DeflaterOutputStream was created with the correct + // buffer. + assertEquals("Incorrect Buffer Size", 512, dos.getProtectedBuf().length); + + dos.write(byteArray); + dos.close(); + f1.delete(); + } + + /** + * @tests java.util.zip.DeflaterOutputStream#DeflaterOutputStream(java.io.OutputStream) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DeflaterOutputStream", + args = {java.io.OutputStream.class} + ) + public void test_ConstructorLjava_io_OutputStream() throws Exception { + File f1 = new File("hyts_Constru_O.tst"); + FileOutputStream fos = new FileOutputStream(f1); + MyDeflaterOutputStream dos = new MyDeflaterOutputStream(fos); + + // Test to see if DeflaterOutputStream was created with the correct + // buffer. + assertEquals("Incorrect Buffer Size", 512, dos.getProtectedBuf().length); + + dos.write(outPutBuf); + dos.close(); + f1.delete(); + } + + /** + * @tests java.util.zip.DeflaterOutputStream#DeflaterOutputStream(java.io.OutputStream, + * java.util.zip.Deflater, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "DeflaterOutputStream", + args = {java.io.OutputStream.class, java.util.zip.Deflater.class, int.class} + ) + public void test_ConstructorLjava_io_OutputStreamLjava_util_zip_DeflaterI() + throws Exception { + int buf = 5; + int negBuf = -5; + int zeroBuf = 0; + byte byteArray[] = {1, 3, 4, 7, 8, 3, 6}; + File f1 = new File("hyts_Constru_ODI.tst"); + FileOutputStream fos = new FileOutputStream(f1); + Deflater defl = null; + MyDeflaterOutputStream dos; + + // Test for a null Deflater. + try { + dos = new MyDeflaterOutputStream(fos, defl, buf); + fail("NullPointerException Not Thrown"); + } catch (NullPointerException e) { + } + defl = new Deflater(); + + // Test for a negative buf. + try { + dos = new MyDeflaterOutputStream(fos, defl, negBuf); + fail("IllegalArgumentException Not Thrown"); + } catch (IllegalArgumentException e) { + } + + // Test for a zero buf. + try { + dos = new MyDeflaterOutputStream(fos, defl, zeroBuf); + fail("IllegalArgumentException Not Thrown"); + } catch (IllegalArgumentException e) { + } + + // Test to see if DeflaterOutputStream was created with the correct + // buffer. + dos = new MyDeflaterOutputStream(fos, defl, buf); + assertEquals("Incorrect Buffer Size", 5, dos.getProtectedBuf().length); + + dos.write(byteArray); + dos.close(); + f1.delete(); + } + + /** + * @tests java.util.zip.DeflaterOutputStream#close() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "IOException can not be checked.", + method = "close", + args = {} + ) + public void test_close() throws Exception { + File f1 = new File("close.tst"); + FileOutputStream fos = new FileOutputStream(f1); + DeflaterOutputStream dos = new DeflaterOutputStream(fos); + byte byteArray[] = {1, 3, 4, 6}; + dos.write(byteArray); + + FileInputStream fis = new FileInputStream(f1); + InflaterInputStream iis = new InflaterInputStream(fis); + try { + iis.read(); + fail("EOFException Not Thrown"); + } catch (EOFException e) { + } + + dos.close(); + + // Test to see if the finish method wrote the bytes to the file. + assertEquals("Incorrect Byte Returned.", 1, iis.read()); + assertEquals("Incorrect Byte Returned.", 3, iis.read()); + assertEquals("Incorrect Byte Returned.", 4, iis.read()); + assertEquals("Incorrect Byte Returned.", 6, iis.read()); + assertEquals("Incorrect Byte Returned.", -1, iis.read()); + assertEquals("Incorrect Byte Returned.", -1, iis.read()); + iis.close(); + + // Not sure if this test will stay. + FileOutputStream fos2 = new FileOutputStream(f1); + DeflaterOutputStream dos2 = new DeflaterOutputStream(fos2); + fos2.close(); + try { + dos2.close(); + fail("IOException not thrown"); + } catch (IOException e) { + } + + // Test to write to a closed DeflaterOutputStream + try { + dos.write(5); + fail("DeflaterOutputStream Able To Write After Being Closed."); + } catch (IOException e) { + } + + // Test to write to a FileOutputStream that should have been closed + // by + // the DeflaterOutputStream. + try { + fos.write(("testing").getBytes()); + fail("FileOutputStream Able To Write After Being Closed."); + } catch (IOException e) { + } + + f1.delete(); + } + + /** + * @tests java.util.zip.DeflaterOutputStream#finish() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "finish", + args = {} + ) + public void test_finish() throws Exception { + // Need test to see if method finish() actually finishes + // Only testing possible errors, not if it actually works + + File f1 = new File("finish.tst"); + FileOutputStream fos1 = new FileOutputStream(f1); + DeflaterOutputStream dos = new DeflaterOutputStream(fos1); + byte byteArray[] = {1, 3, 4, 6}; + dos.write(byteArray); + dos.finish(); + + // Test to see if the same FileOutputStream can be used with the + // DeflaterOutputStream after finish is called. + try { + dos.write(1); + fail("IOException not thrown"); + } catch (IOException e) { + } + + // Test for writing with a new FileOutputStream using the same + // DeflaterOutputStream. + FileOutputStream fos2 = new FileOutputStream(f1); + dos = new DeflaterOutputStream(fos2); + dos.write(1); + + // Test for writing to FileOutputStream fos1, which should be open. + fos1.write(("testing").getBytes()); + + // Test for writing to FileOutputStream fos2, which should be open. + fos2.write(("testing").getBytes()); + + // Not sure if this test will stay. + FileOutputStream fos3 = new FileOutputStream(f1); + DeflaterOutputStream dos3 = new DeflaterOutputStream(fos3); + fos3.close(); + try { + dos3.finish(); + fail("IOException not thrown"); + } catch (IOException e) { + } + + // dos.close() won't close fos1 because it has been re-assigned to + // fos2 + fos1.close(); + dos.close(); + f1.delete(); + } + + /** + * @tests java.util.zip.DeflaterOutputStream#write(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "write", + args = {int.class} + ) + public void test_writeI() throws Exception { + File f1 = new File("writeI1.tst"); + FileOutputStream fos = new FileOutputStream(f1); + DeflaterOutputStream dos = new DeflaterOutputStream(fos); + for (int i = 0; i < 3; i++) { + dos.write(i); + } + dos.close(); + FileInputStream fis = new FileInputStream(f1); + InflaterInputStream iis = new InflaterInputStream(fis); + for (int i = 0; i < 3; i++) { + assertEquals("Incorrect Byte Returned.", i, iis.read()); + } + assertEquals("Incorrect Byte Returned (EOF).", -1, iis.read()); + assertEquals("Incorrect Byte Returned (EOF).", -1, iis.read()); + iis.close(); + + // Not sure if this test is that important. + // Checks to see if you can write using the DeflaterOutputStream + // after + // the FileOutputStream has been closed. + FileOutputStream fos2 = new FileOutputStream(f1); + DeflaterOutputStream dos2 = new DeflaterOutputStream(fos2); + fos2.close(); + try { + dos2.write(2); + fail("IOException not thrown"); + } catch (IOException e) { + } + + f1.delete(); + } + + /** + * @tests java.util.zip.DeflaterOutputStream#write(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "write", + args = {byte[].class, int.class, int.class} + ) + public void test_write$BII() throws Exception { + byte byteArray[] = {1, 3, 4, 7, 8, 3, 6}; + + // Test to see if the correct bytes are saved. + File f1 = new File("writeBII.tst"); + FileOutputStream fos1 = new FileOutputStream(f1); + DeflaterOutputStream dos1 = new DeflaterOutputStream(fos1); + dos1.write(byteArray, 2, 3); + dos1.close(); + FileInputStream fis = new FileInputStream(f1); + InflaterInputStream iis = new InflaterInputStream(fis); + assertEquals("Incorrect Byte Returned.", 4, iis.read()); + assertEquals("Incorrect Byte Returned.", 7, iis.read()); + assertEquals("Incorrect Byte Returned.", 8, iis.read()); + assertEquals("Incorrect Byte Returned (EOF).", -1, iis.read()); + assertEquals("Incorrect Byte Returned (EOF).", -1, iis.read()); + iis.close(); + f1.delete(); + + // Test for trying to write more bytes than available from the array + File f2 = new File("writeBII2.tst"); + FileOutputStream fos2 = new FileOutputStream(f2); + DeflaterOutputStream dos2 = new DeflaterOutputStream(fos2); + try { + dos2.write(byteArray, 2, 10); + fail("IndexOutOfBoundsException not thrown"); + } catch (IndexOutOfBoundsException e) { + } + + // Test for trying to write a negative number of bytes. + try { + dos2.write(byteArray, 2, Integer.MIN_VALUE); + fail("IndexOutOfBoundsException not thrown"); + } catch (IndexOutOfBoundsException e) { + } + + // Test for trying to start writing from a byte < 0 from the array. + try { + dos2.write(byteArray, Integer.MIN_VALUE, 2); + fail("IndexOutOfBoundsException not thrown"); + } catch (IndexOutOfBoundsException e) { + } + + // Test for trying to start writing from a byte > than the array + // size. + try { + dos2.write(byteArray, 10, 2); + fail("IndexOutOfBoundsException not thrown"); + } catch (IndexOutOfBoundsException e) { + } + dos2.close(); + + // Not sure if this test is that important. + // Checks to see if you can write using the DeflaterOutputStream + // after + // the FileOutputStream has been closed. + FileOutputStream fos3 = new FileOutputStream(f2); + DeflaterOutputStream dos3 = new DeflaterOutputStream(fos3); + fos3.close(); + try { + dos3.write(byteArray, 2, 3); + fail("IOException not thrown"); + } catch (IOException e) { + } + + f2.delete(); + } + + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "deflate", + args = {} + ) + public void test_deflate() throws Exception { + File f1 = new File("writeI1.tst"); + FileOutputStream fos = new FileOutputStream(f1); + MyDeflaterOutputStream dos = new MyDeflaterOutputStream(fos); + assertFalse(dos.getDaflateFlag()); + for (int i = 0; i < 3; i++) { + dos.write(i); + } + assertTrue(dos.getDaflateFlag()); + dos.close(); + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/DeflaterTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/DeflaterTest.java new file mode 100644 index 0000000..ae77450 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/DeflaterTest.java @@ -0,0 +1,1243 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.util.zip.Adler32; + +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import junit.framework.TestCase; +import tests.support.resource.Support_Resources; + +@TestTargetClass(Deflater.class) +public class DeflaterTest extends TestCase { + + class MyDeflater extends Deflater { + MyDeflater() { + super(); + } + + MyDeflater(int lvl) { + super(lvl); + } + + MyDeflater(int lvl, boolean noHeader) { + super(lvl, noHeader); + } + + void myFinalize() { + finalize(); + } + + int getDefCompression() { + return DEFAULT_COMPRESSION; + } + + int getDefStrategy() { + return DEFAULT_STRATEGY; + } + + int getHuffman() { + return HUFFMAN_ONLY; + } + + int getFiltered() { + return FILTERED; + } + } + + /** + * @tests java.util.zip.Deflater#deflate(byte[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "deflate", + args = {byte[].class} + ) + public void test_deflate$B() { + byte outPutBuf[] = new byte[50]; + byte byteArray[] = {1, 3, 4, 7, 8}; + byte outPutInf[] = new byte[50]; + int x = 0; + + Deflater defl = new Deflater(); + defl.setInput(byteArray); + defl.finish(); + while (!defl.finished()) { + x += defl.deflate(outPutBuf); + } + assertEquals("Deflater at end of stream, should return 0", 0, defl + .deflate(outPutBuf)); + int totalOut = defl.getTotalOut(); + int totalIn = defl.getTotalIn(); + assertEquals(x, totalOut); + assertEquals(byteArray.length, totalIn); + defl.end(); + + Inflater infl = new Inflater(); + try { + infl.setInput(outPutBuf); + while (!infl.finished()) { + infl.inflate(outPutInf); + } + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } + assertEquals(totalIn, infl.getTotalOut()); + assertEquals(totalOut, infl.getTotalIn()); + for (int i = 0; i < byteArray.length; i++) { + assertEquals(byteArray[i], outPutInf[i]); + } + assertEquals( + "Final decompressed data contained more bytes than original", + 0, outPutInf[byteArray.length]); + infl.end(); + } + + /** + * @tests java.util.zip.Deflater#deflate(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "deflate", + args = {byte[].class, int.class, int.class} + ) + public void test_deflate$BII() { + byte outPutBuf[] = new byte[50]; + byte byteArray[] = {5, 2, 3, 7, 8}; + byte outPutInf[] = new byte[50]; + int offSet = 1; + int length = outPutBuf.length - 1; + int x = 0; + + Deflater defl = new Deflater(); + defl.setInput(byteArray); + defl.finish(); + while (!defl.finished()) { + x += defl.deflate(outPutBuf, offSet, length); + } + assertEquals("Deflater at end of stream, should return 0", 0, defl + .deflate(outPutBuf, offSet, length)); + int totalOut = defl.getTotalOut(); + int totalIn = defl.getTotalIn(); + assertEquals(x, totalOut); + assertEquals(byteArray.length, totalIn); + defl.end(); + + Inflater infl = new Inflater(); + try { + infl.setInput(outPutBuf, offSet, length); + while (!infl.finished()) { + infl.inflate(outPutInf); + } + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } + assertEquals(totalIn, infl.getTotalOut()); + assertEquals(totalOut, infl.getTotalIn()); + for (int i = 0; i < byteArray.length; i++) { + assertEquals(byteArray[i], outPutInf[i]); + } + assertEquals( + "Final decompressed data contained more bytes than original", + 0, outPutInf[byteArray.length]); + infl.end(); + + // Set of tests testing the boundaries of the offSet/length + defl = new Deflater(); + outPutBuf = new byte[100]; + defl.setInput(byteArray); + for (int i = 0; i < 2; i++) { + if (i == 0) { + offSet = outPutBuf.length + 1; + length = outPutBuf.length; + } else { + offSet = 0; + length = outPutBuf.length + 1; + } + try { + defl.deflate(outPutBuf, offSet, length); + fail("Test " + i + + ": ArrayIndexOutOfBoundsException not thrown"); + } catch (ArrayIndexOutOfBoundsException e) { + } + } + defl.end(); + } + + /** + * @tests java.util.zip.Deflater#end() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "end", + args = {} + ) + public void test_end() { + byte byteArray[] = {5, 2, 3, 7, 8}; + byte outPutBuf[] = new byte[100]; + + Deflater defl = new Deflater(); + defl.setInput(byteArray); + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + defl.end(); + helper_end_test(defl, "end"); + } + + /** + * @tests java.util.zip.Deflater#finalize() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "finalize", + args = {} + ) + public void test_finalize() { + MyDeflater mdefl = new MyDeflater(); + mdefl.myFinalize(); + System.gc(); + helper_end_test(mdefl, "finalize"); + } + + /** + * @tests java.util.zip.Deflater#finish() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "finish", + args = {} + ) + public void test_finish() throws Exception { + // This test already here, its the same as test_deflate() + byte byteArray[] = {5, 2, 3, 7, 8}; + byte outPutBuf[] = new byte[100]; + byte outPutInf[] = new byte[100]; + int x = 0; + Deflater defl = new Deflater(); + defl.setInput(byteArray); + defl.finish(); + + // needsInput should never return true after finish() is called + if (System.getProperty("java.vendor").startsWith("IBM")) { + assertFalse( + "needsInput() should return false after finish() is called", + defl.needsInput()); + } + + while (!defl.finished()) { + x += defl.deflate(outPutBuf); + } + int totalOut = defl.getTotalOut(); + int totalIn = defl.getTotalIn(); + assertEquals(x, totalOut); + assertEquals(byteArray.length, totalIn); + defl.end(); + + Inflater infl = new Inflater(); + infl.setInput(outPutBuf); + while (!infl.finished()) { + infl.inflate(outPutInf); + } + assertEquals(totalIn, infl.getTotalOut()); + assertEquals(totalOut, infl.getTotalIn()); + for (int i = 0; i < byteArray.length; i++) { + assertEquals(byteArray[i], outPutInf[i]); + } + assertEquals( + "Final decompressed data contained more bytes than original", + 0, outPutInf[byteArray.length]); + infl.end(); + } + + /** + * @tests java.util.zip.Deflater#finished() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "finished", + args = {} + ) + public void test_finished() { + byte byteArray[] = {5, 2, 3, 7, 8}; + byte outPutBuf[] = new byte[100]; + Deflater defl = new Deflater(); + assertTrue("Test 1: Deflater should not be finished.", !defl.finished()); + defl.setInput(byteArray); + assertTrue("Test 2: Deflater should not be finished.", !defl.finished()); + defl.finish(); + assertTrue("Test 3: Deflater should not be finished.", !defl.finished()); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + assertTrue("Test 4: Deflater should be finished.", defl.finished()); + defl.end(); + assertTrue("Test 5: Deflater should be finished.", defl.finished()); + } + + /** + * @tests java.util.zip.Deflater#getAdler() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAdler", + args = {} + ) + public void test_getAdler() { + byte byteArray[] = {'a', 'b', 'c', 1, 2, 3}; + byte outPutBuf[] = new byte[100]; + Deflater defl = new Deflater(); + + // getting the checkSum value using the Adler + defl.setInput(byteArray); + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + long checkSumD = defl.getAdler(); + defl.end(); + + // getting the checkSum value through the Adler32 class + Adler32 adl = new Adler32(); + adl.update(byteArray); + long checkSumR = adl.getValue(); + assertEquals( + "The checksum value returned by getAdler() is not the same as the checksum returned by creating the adler32 instance", + checkSumD, checkSumR); + } + + /** + * @tests java.util.zip.Deflater#getTotalIn() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getTotalIn", + args = {} + ) + public void test_getTotalIn() { + byte outPutBuf[] = new byte[5]; + byte byteArray[] = {1, 3, 4, 7, 8}; + + Deflater defl = new Deflater(); + defl.setInput(byteArray); + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + assertEquals(byteArray.length, defl.getTotalIn()); + defl.end(); + + defl = new Deflater(); + int offSet = 2; + int length = 3; + outPutBuf = new byte[5]; + defl.setInput(byteArray, offSet, length); + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + assertEquals(length, defl.getTotalIn()); + defl.end(); + } + + /** + * @tests java.util.zip.Deflater#getTotalOut() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getTotalOut", + args = {} + ) + public void test_getTotalOut() { + // the getTotalOut should equal the sum of value returned by deflate() + byte outPutBuf[] = new byte[5]; + byte byteArray[] = {5, 2, 3, 7, 8}; + int x = 0; + Deflater defl = new Deflater(); + defl.setInput(byteArray); + defl.finish(); + while (!defl.finished()) { + x += defl.deflate(outPutBuf); + } + assertEquals(x, defl.getTotalOut()); + defl.end(); + + x = 0; + int offSet = 2; + int length = 3; + defl = new Deflater(); + outPutBuf = new byte[5]; + defl.setInput(byteArray, offSet, length); + defl.finish(); + while (!defl.finished()) { + x += defl.deflate(outPutBuf); + } + assertEquals(x, defl.getTotalOut()); + } + + /** + * @tests java.util.zip.Deflater#needsInput() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "needsInput", + args = {} + ) + public void test_needsInput() { + Deflater defl = new Deflater(); + assertTrue( + "needsInput give the wrong boolean value as a result of no input buffer", + defl.needsInput()); + byte byteArray[] = {1, 2, 3}; + defl.setInput(byteArray); + assertFalse( + "needsInput give wrong boolean value as a result of a full input buffer", + defl.needsInput()); + byte[] outPutBuf = new byte[50]; + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + byte emptyByteArray[] = new byte[0]; + defl.setInput(emptyByteArray); + assertTrue( + "needsInput give wrong boolean value as a result of an empty input buffer", + defl.needsInput()); + defl.setInput(byteArray); + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + // needsInput should NOT return true after finish() has been + // called. + if (System.getProperty("java.vendor").startsWith("IBM")) { + assertFalse( + "needsInput gave wrong boolean value as a result of finish() being called", + defl.needsInput()); + } + defl.end(); + } + + /** + * @tests java.util.zip.Deflater#reset() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "reset", + args = {} + ) + public void test_reset() { + byte outPutBuf[] = new byte[100]; + byte outPutInf[] = new byte[100]; + byte curArray[] = new byte[5]; + byte byteArray[] = {1, 3, 4, 7, 8}; + byte byteArray2[] = {8, 7, 4, 3, 1}; + int x = 0; + int orgValue = 0; + Deflater defl = new Deflater(); + + for (int i = 0; i < 3; i++) { + if (i == 0) { + curArray = byteArray; + } else if (i == 1) { + curArray = byteArray2; + } else { + defl.reset(); + } + + defl.setInput(curArray); + defl.finish(); + while (!defl.finished()) { + x += defl.deflate(outPutBuf); + } + + if (i == 0) { + assertEquals(x, defl.getTotalOut()); + } else if (i == 1) { + assertEquals(x, orgValue); + } else { + assertEquals(x, orgValue * 2); + } + + if (i == 0) { + orgValue = x; + } + + try { + Inflater infl = new Inflater(); + infl.setInput(outPutBuf); + while (!infl.finished()) { + infl.inflate(outPutInf); + } + infl.end(); + } catch (DataFormatException e) { + fail("Test " + i + ": Invalid input to be decompressed"); + } + + if (i == 1) { + curArray = byteArray; + } + + for (int j = 0; j < curArray.length; j++) { + assertEquals(curArray[j], outPutInf[j]); + } + assertEquals(0, outPutInf[curArray.length]); + } + } + + /** + * @tests java.util.zip.Deflater#setDictionary(byte[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setDictionary", + args = {byte[].class} + ) + public void test_setDictionary$B() { + // This test is very close to getAdler() + byte dictionaryArray[] = {'e', 'r', 't', 'a', 'b', 2, 3}; + byte byteArray[] = { + 4, 5, 3, 2, 'a', 'b', 6, 7, 8, 9, 0, 's', '3', 'w', 'r'}; + byte outPutBuf[] = new byte[100]; + + Deflater defl = new Deflater(); + long deflAdler = defl.getAdler(); + assertEquals( + "No dictionary set, no data deflated, getAdler should return 1", + 1, deflAdler); + defl.setDictionary(dictionaryArray); + deflAdler = defl.getAdler(); + + // getting the checkSum value through the Adler32 class + Adler32 adl = new Adler32(); + adl.update(dictionaryArray); + long realAdler = adl.getValue(); + assertEquals(deflAdler, realAdler); + + defl.setInput(byteArray); + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + deflAdler = defl.getAdler(); + adl = new Adler32(); + adl.update(byteArray); + realAdler = adl.getValue(); + // Deflate is finished and there were bytes deflated that did not occur + // in the dictionaryArray, therefore a new dictionary was automatically + // set. + assertEquals(realAdler, deflAdler); + defl.end(); + } + + /** + * @tests java.util.zip.Deflater#setDictionary(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setDictionary", + args = {byte[].class, int.class, int.class} + ) + public void test_setDictionary$BII() { + // This test is very close to getAdler() + byte dictionaryArray[] = {'e', 'r', 't', 'a', 'b', 2, 3, 'o', 't'}; + byte byteArray[] = { + 4, 5, 3, 2, 'a', 'b', 6, 7, 8, 9, 0, 's', '3', 'w', 'r', 't', + 'u', 'i', 'o', 4, 5, 6, 7}; + byte outPutBuf[] = new byte[500]; + + int offSet = 4; + int length = 5; + + Deflater defl = new Deflater(); + long deflAdler = defl.getAdler(); + assertEquals( + "No dictionary set, no data deflated, getAdler should return 1", + 1, deflAdler); + defl.setDictionary(dictionaryArray, offSet, length); + deflAdler = defl.getAdler(); + + // getting the checkSum value through the Adler32 class + Adler32 adl = new Adler32(); + adl.update(dictionaryArray, offSet, length); + long realAdler = adl.getValue(); + assertEquals(deflAdler, realAdler); + + defl.setInput(byteArray); + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + deflAdler = defl.getAdler(); + adl = new Adler32(); + adl.update(byteArray); + realAdler = adl.getValue(); + // Deflate is finished and there were bytes deflated that did not occur + // in the dictionaryArray, therefore a new dictionary was automatically + // set. + assertEquals(realAdler, deflAdler); + defl.end(); + + // boundary check + defl = new Deflater(); + for (int i = 0; i < 2; i++) { + if (i == 0) { + offSet = 0; + length = dictionaryArray.length + 1; + } else { + offSet = dictionaryArray.length + 1; + length = 1; + } + try { + defl.setDictionary(dictionaryArray, offSet, length); + fail("Test " + + i + + ": boundary check for setDictionary failed for offset " + + offSet + " and length " + length); + } catch (ArrayIndexOutOfBoundsException e) { + } + } + } + + /** + * @tests java.util.zip.Deflater#setInput(byte[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setInput", + args = {byte[].class} + ) + public void test_setInput$B() { + byte[] byteArray = {1, 2, 3}; + byte[] outPutBuf = new byte[50]; + byte[] outPutInf = new byte[50]; + + Deflater defl = new Deflater(); + defl.setInput(byteArray); + assertTrue("the array buffer in setInput() is empty", !defl + .needsInput()); + // The second setInput() should be ignored since needsInput() return + // false + defl.setInput(byteArray); + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + defl.end(); + + Inflater infl = new Inflater(); + try { + infl.setInput(outPutBuf); + while (!infl.finished()) { + infl.inflate(outPutInf); + } + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } + for (int i = 0; i < byteArray.length; i++) { + assertEquals(byteArray[i], outPutInf[i]); + } + assertEquals(byteArray.length, infl.getTotalOut()); + infl.end(); + } + + /** + * @tests java.util.zip.Deflater#setInput(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setInput", + args = {byte[].class, int.class, int.class} + ) + public void test_setInput$BII() throws Exception { + byte[] byteArray = {1, 2, 3, 4, 5}; + byte[] outPutBuf = new byte[50]; + byte[] outPutInf = new byte[50]; + int offSet = 1; + int length = 3; + + Deflater defl = new Deflater(); + defl.setInput(byteArray, offSet, length); + assertFalse("the array buffer in setInput() is empty", defl + .needsInput()); + // The second setInput() should be ignored since needsInput() return + // false + defl.setInput(byteArray, offSet, length); + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + defl.end(); + + Inflater infl = new Inflater(); + infl.setInput(outPutBuf); + while (!infl.finished()) { + infl.inflate(outPutInf); + } + for (int i = 0; i < length; i++) { + assertEquals(byteArray[i + offSet], outPutInf[i]); + } + assertEquals(length, infl.getTotalOut()); + infl.end(); + + // boundary check + defl = new Deflater(); + for (int i = 0; i < 2; i++) { + if (i == 0) { + offSet = 0; + length = byteArray.length + 1; + } else { + offSet = byteArray.length + 1; + length = 1; + } + try { + defl.setInput(byteArray, offSet, length); + fail("Test " + i + + ": boundary check for setInput failed for offset " + + offSet + " and length " + length); + } catch (ArrayIndexOutOfBoundsException e) { + } + } + } + + /** + * @tests java.util.zip.Deflater#setLevel(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setLevel", + args = {int.class} + ) + public void test_setLevelI() throws Exception { + // Very similar to test_Constructor(int) + byte[] byteArray = new byte[100]; + InputStream inFile = Support_Resources.getStream("hyts_checkInput.txt"); + inFile.read(byteArray); + inFile.close(); + + byte[] outPutBuf; + int totalOut; + for (int i = 0; i < 10; i++) { + Deflater defl = new Deflater(); + defl.setLevel(i); + outPutBuf = new byte[500]; + defl.setInput(byteArray); + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + totalOut = defl.getTotalOut(); + defl.end(); + + outPutBuf = new byte[500]; + defl = new Deflater(i); + defl.setInput(byteArray); + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + assertEquals(totalOut, defl.getTotalOut()); + defl.end(); + } + + // testing boundaries + try { + Deflater boundDefl = new Deflater(); + // Level must be between 0-9 + boundDefl.setLevel(-2); + fail("IllegalArgumentException not thrown when setting level to a number < 0."); + } catch (IllegalArgumentException e) { + } + try { + Deflater boundDefl = new Deflater(); + boundDefl.setLevel(10); + fail("IllegalArgumentException not thrown when setting level to a number > 9."); + } catch (IllegalArgumentException e) { + } + } + + /** + * @tests java.util.zip.Deflater#setStrategy(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setStrategy", + args = {int.class} + ) + public void test_setStrategyI() throws Exception { + byte[] byteArray = new byte[100]; + InputStream inFile = Support_Resources.getStream("hyts_checkInput.txt"); + inFile.read(byteArray); + inFile.close(); + + for (int i = 0; i < 3; i++) { + byte outPutBuf[] = new byte[500]; + MyDeflater mdefl = new MyDeflater(); + + if (i == 0) { + mdefl.setStrategy(mdefl.getDefStrategy()); + } else if (i == 1) { + mdefl.setStrategy(mdefl.getHuffman()); + } else { + mdefl.setStrategy(mdefl.getFiltered()); + } + + mdefl.setInput(byteArray); + while (!mdefl.needsInput()) { + mdefl.deflate(outPutBuf); + } + mdefl.finish(); + while (!mdefl.finished()) { + mdefl.deflate(outPutBuf); + } + + if (i == 0) { + // System.out.println(mdefl.getTotalOut()); + // ran JDK and found that getTotalOut() = 86 for this particular + // file + assertEquals( + "getTotalOut() for the default strategy did not correspond with JDK", + 86, mdefl.getTotalOut()); + } else if (i == 1) { + // System.out.println(mdefl.getTotalOut()); + // ran JDK and found that getTotalOut() = 100 for this + // particular file + assertEquals( + "getTotalOut() for the Huffman strategy did not correspond with JDK", + 100, mdefl.getTotalOut()); + } else { + // System.out.println(mdefl.getTotalOut()); + // ran JDK and found that totalOut = 93 for this particular file + assertEquals( + "Total Out for the Filtered strategy did not correspond with JDK", + 93, mdefl.getTotalOut()); + } + mdefl.end(); + } + + // Attempting to setStrategy to an invalid value + try { + Deflater defl = new Deflater(); + defl.setStrategy(-412); + fail("IllegalArgumentException not thrown when setting strategy to an invalid value."); + } catch (IllegalArgumentException e) { + } + } + + /** + * @tests java.util.zip.Deflater#Deflater() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Deflater", + args = {} + ) + public void test_Constructor() throws Exception { + byte[] byteArray = new byte[100]; + InputStream inFile = Support_Resources.getStream("hyts_checkInput.txt"); + inFile.read(byteArray); + inFile.close(); + + Deflater defl = new Deflater(); + byte[] outPutBuf = new byte[500]; + defl.setInput(byteArray); + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + int totalOut = defl.getTotalOut(); + defl.end(); + + // creating a Deflater using the DEFAULT_COMPRESSION as the int + MyDeflater mdefl = new MyDeflater(); + mdefl = new MyDeflater(mdefl.getDefCompression()); + outPutBuf = new byte[500]; + mdefl.setInput(byteArray); + while (!mdefl.needsInput()) { + mdefl.deflate(outPutBuf); + } + mdefl.finish(); + while (!mdefl.finished()) { + mdefl.deflate(outPutBuf); + } + assertEquals(totalOut, mdefl.getTotalOut()); + mdefl.end(); + } + + /** + * @tests java.util.zip.Deflater#Deflater(int, boolean) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Deflater", + args = {int.class, boolean.class} + ) + public void test_ConstructorIZ() throws Exception { + byte byteArray[] = { + 4, 5, 3, 2, 'a', 'b', 6, 7, 8, 9, 0, 's', '3', 'w', 'r'}; + + Deflater defl = new Deflater(); + byte outPutBuf[] = new byte[500]; + defl.setLevel(2); + defl.setInput(byteArray); + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + int totalOut = defl.getTotalOut(); + defl.end(); + + outPutBuf = new byte[500]; + defl = new Deflater(2, false); + defl.setInput(byteArray); + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + assertEquals(totalOut, defl.getTotalOut()); + defl.end(); + + outPutBuf = new byte[500]; + defl = new Deflater(2, true); + defl.setInput(byteArray); + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + assertTrue( + "getTotalOut() should not be equal comparing two Deflaters with different header options.", + defl.getTotalOut() != totalOut); + defl.end(); + + byte outPutInf[] = new byte[500]; + Inflater infl = new Inflater(true); + while (!infl.finished()) { + if (infl.needsInput()) { + infl.setInput(outPutBuf); + } + infl.inflate(outPutInf); + } + for (int i = 0; i < byteArray.length; i++) { + assertEquals(byteArray[i], outPutInf[i]); + } + assertEquals( + "final decompressed data contained more bytes than original - constructorIZ", + 0, outPutInf[byteArray.length]); + infl.end(); + + infl = new Inflater(false); + outPutInf = new byte[500]; + int r = 0; + try { + while (!infl.finished()) { + if (infl.needsInput()) { + infl.setInput(outPutBuf); + } + infl.inflate(outPutInf); + } + } catch (DataFormatException e) { + r = 1; + } + assertEquals("header option did not correspond", 1, r); + + // testing boundaries + try { + Deflater boundDefl = new Deflater(); + // Level must be between 0-9 + boundDefl.setLevel(-2); + fail("IllegalArgumentException not thrown when setting level to a number < 0."); + } catch (IllegalArgumentException e) { + } + try { + Deflater boundDefl = new Deflater(); + boundDefl.setLevel(10); + fail("IllegalArgumentException not thrown when setting level to a number > 9."); + } catch (IllegalArgumentException e) { + } + } + + /** + * @tests java.util.zip.Deflater#Deflater(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Deflater", + args = {int.class} + ) + public void test_ConstructorI() throws Exception { + byte[] byteArray = new byte[100]; + InputStream inFile = Support_Resources.getStream("hyts_checkInput.txt"); + inFile.read(byteArray); + inFile.close(); + + byte outPutBuf[] = new byte[500]; + Deflater defl = new Deflater(3); + defl.setInput(byteArray); + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + int totalOut = defl.getTotalOut(); + defl.end(); + + // test to see if the compression ratio is the same as setting the level + // on a deflater + outPutBuf = new byte[500]; + defl = new Deflater(); + defl.setLevel(3); + defl.setInput(byteArray); + while (!defl.needsInput()) { + defl.deflate(outPutBuf); + } + defl.finish(); + while (!defl.finished()) { + defl.deflate(outPutBuf); + } + assertEquals(totalOut, defl.getTotalOut()); + defl.end(); + + // testing boundaries + try { + Deflater boundDefl = new Deflater(); + // Level must be between 0-9 + boundDefl.setLevel(-2); + fail("IllegalArgumentException not thrown when setting level to a number < 0."); + } catch (IllegalArgumentException e) { + } + try { + Deflater boundDefl = new Deflater(); + boundDefl.setLevel(10); + fail("IllegalArgumentException not thrown when setting level to a number > 9."); + } catch (IllegalArgumentException e) { + } + } + + private void helper_end_test(Deflater defl, String desc) { + // Help tests for test_end() and test_reset(). + byte byteArray[] = {5, 2, 3, 7, 8}; + + // Methods where we expect IllegalStateException or NullPointerException + // to be thrown + try { + defl.getTotalOut(); + fail("defl.getTotalOut() can still be used after " + desc + + " is called in test_" + desc); + } catch (IllegalStateException e) { + } catch (NullPointerException e) { + } + try { + defl.getTotalIn(); + fail("defl.getTotalIn() can still be used after " + desc + + " is called in test_" + desc); + } catch (IllegalStateException e) { + } catch (NullPointerException e) { + } + try { + defl.getAdler(); + fail("defl.getAdler() can still be used after " + desc + + " is called in test_" + desc); + } catch (IllegalStateException e) { + } catch (NullPointerException e) { + } + try { + byte[] dict = {'a', 'b', 'c'}; + defl.setDictionary(dict); + fail("defl.setDictionary() can still be used after " + desc + + " is called in test_" + desc); + } catch (IllegalStateException e) { + } catch (NullPointerException e) { + } + try { + defl.getTotalIn(); + fail("defl.getTotalIn() can still be used after " + desc + + " is called in test_" + desc); + } catch (IllegalStateException e) { + } catch (NullPointerException e) { + } + try { + defl.getTotalIn(); + fail("defl.getTotalIn() can still be used after " + desc + + " is called in test_" + desc); + } catch (IllegalStateException e) { + } catch (NullPointerException e) { + } + try { + defl.deflate(byteArray); + fail("defl.deflate() can still be used after " + desc + + " is called in test_" + desc); + } catch (IllegalStateException e) { + } catch (NullPointerException e) { + } + + // Methods where we expect NullPointerException to be thrown + try { + defl.reset(); + fail("defl.reset() can still be used after " + desc + + " is called in test_" + desc); + } catch (NullPointerException e) { + } + + // Methods that should be allowed to be called after end() is called + defl.needsInput(); + defl.setStrategy(1); + defl.setLevel(1); + defl.end(); + + // Methods where exceptions should be thrown + String vendor = System.getProperty("java.vendor"); + if (vendor.indexOf("IBM") != -1) { + try { + defl.setInput(byteArray); + fail("defl.setInput() can still be used after " + desc + + " is called in test_" + desc); + } catch (IllegalStateException e) { + } + } + } + + /** + * @throws DataFormatException + * @throws UnsupportedEncodingException + * @tests java.util.zip.Deflater#getBytesRead() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getBytesRead", + args = {} + ) + public void test_getBytesRead() throws DataFormatException, + UnsupportedEncodingException { + // Regression test for HARMONY-158 + Deflater def = new Deflater(); + assertEquals(0, def.getTotalIn()); + assertEquals(0, def.getTotalOut()); + assertEquals(0, def.getBytesRead()); + // Encode a String into bytes + String inputString = "blahblahblah??"; + byte[] input = inputString.getBytes("UTF-8"); + + // Compress the bytes + byte[] output = new byte[100]; + def.setInput(input); + def.finish(); + int compressedDataLength = def.deflate(output); + assertEquals(14, def.getTotalIn()); + assertEquals(compressedDataLength, def.getTotalOut()); + assertEquals(14, def.getBytesRead()); + } + + /** + * @throws DataFormatException + * @throws UnsupportedEncodingException + * @tests java.util.zip.Deflater#getBytesRead() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getBytesWritten", + args = {} + ) + public void test_getBytesWritten() throws DataFormatException, + UnsupportedEncodingException { + // Regression test for HARMONY-158 + Deflater def = new Deflater(); + assertEquals(0, def.getTotalIn()); + assertEquals(0, def.getTotalOut()); + assertEquals(0, def.getBytesWritten()); + // Encode a String into bytes + String inputString = "blahblahblah??"; + byte[] input = inputString.getBytes("UTF-8"); + + // Compress the bytes + byte[] output = new byte[100]; + def.setInput(input); + def.finish(); + int compressedDataLength = def.deflate(output); + assertEquals(14, def.getTotalIn()); + assertEquals(compressedDataLength, def.getTotalOut()); + assertEquals(compressedDataLength, def.getBytesWritten()); + } + + // BEGIN android-removed + // We use different default settings for deflating, so our output won't be + // the + // same. + // //Regression Test for HARMONY-2481 + // public void test_deflate_beforeSetInput() throws Exception { + // Deflater deflater = new Deflater(); + // deflater.finish(); + // byte[] buffer = new byte[1024]; + // assertEquals(8, deflater.deflate(buffer)); + // byte[] expectedBytes = { 120, -100, 3, 0, 0, 0, 0, 1 }; + // for (int i = 0; i < expectedBytes.length; i++) { + // assertEquals(expectedBytes[i], buffer[i]); + // } + // } + // END android-removed +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/GZIPInputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/GZIPInputStreamTest.java new file mode 100644 index 0000000..75060bd --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/GZIPInputStreamTest.java @@ -0,0 +1,322 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.zip.Checksum; + +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import tests.support.resource.Support_Resources; + +@TestTargetClass(GZIPInputStream.class) +public class GZIPInputStreamTest extends junit.framework.TestCase { + File resources; + + class TestGZIPInputStream extends GZIPInputStream { + TestGZIPInputStream(InputStream in) throws IOException { + super(in); + } + + TestGZIPInputStream(InputStream in, int size) throws IOException { + super(in, size); + } + + Checksum getChecksum() { + return crc; + } + + boolean endofInput() { + return eos; + } + } + + /** + * @tests java.util.zip.GZIPInputStream#GZIPInputStream(java.io.InputStream) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "GZIPInputStream", + args = {java.io.InputStream.class} + ) + public void test_ConstructorLjava_io_InputStream() { + // test method java.util.zip.GZIPInputStream.constructor + try { + Support_Resources.copyFile(resources, "GZIPInputStream", + "hyts_gInput.txt.gz"); + final URL gInput = new File(resources.toString() + + "/GZIPInputStream/hyts_gInput.txt.gz").toURL(); + TestGZIPInputStream inGZIP = new TestGZIPInputStream(gInput + .openConnection().getInputStream()); + assertNotNull("the constructor for GZIPInputStream is null", inGZIP); + assertEquals("the CRC value of the inputStream is not zero", 0, + inGZIP.getChecksum().getValue()); + inGZIP.close(); + } catch (IOException e) { + fail("an IO error occured while trying to open the input file"); + } + } + + /** + * @tests java.util.zip.GZIPInputStream#GZIPInputStream(java.io.InputStream, + * int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "GZIPInputStream", + args = {java.io.InputStream.class, int.class} + ) + public void test_ConstructorLjava_io_InputStreamI() { + // test method java.util.zip.GZIPInputStream.constructorI + try { + Support_Resources.copyFile(resources, "GZIPInputStream", + "hyts_gInput.txt.gz"); + final URL gInput = new File(resources.toString() + + "/GZIPInputStream/hyts_gInput.txt.gz").toURL(); + TestGZIPInputStream inGZIP = new TestGZIPInputStream(gInput + .openConnection().getInputStream(), 200); + assertNotNull("the constructor for GZIPInputStream is null", inGZIP); + assertEquals("the CRC value of the inputStream is not zero", 0, + inGZIP.getChecksum().getValue()); + inGZIP.close(); + } catch (IOException e) { + fail("an IO error occured while trying to open the input file"); + } + try { + Support_Resources.copyFile(resources, "GZIPInputStream", + "hyts_gInput.txt.gz"); + final URL gInput = new File(resources.toString() + + "/GZIPInputStream/hyts_gInput.txt.gz").toURL(); + TestGZIPInputStream inGZIP = new TestGZIPInputStream(gInput + .openConnection().getInputStream(), 0); + fail("Expected IllegalArgumentException"); + } catch (IOException e) { + fail("an IO error occured while trying to open the input file"); + } catch (IllegalArgumentException ee) { + // expected + } + try { + Support_Resources.copyFile(resources, "GZIPInputStream", + "hyts_gInput.txt.gz"); + final URL gInput = new File(resources.toString() + + "/GZIPInputStream/hyts_gInput.txt.gz").toURL(); + TestGZIPInputStream inGZIP = new TestGZIPInputStream(gInput + .openConnection().getInputStream(), -1); + fail("Expected IllegalArgumentException"); + } catch (IOException e) { + fail("an IO error occured while trying to open the input file"); + } catch (IllegalArgumentException ee) { + // expected + } + } + + /** + * @tests java.util.zip.GZIPInputStream#read(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "read", + args = {byte[].class, int.class, int.class} + ) + public void test_read$BII() throws IOException { + // test method java.util.zip.GZIPInputStream.readBII + byte orgBuf[] = {'3', '5', '2', 'r', 'g', 'e', 'f', 'd', 'e', 'w'}; + byte outBuf[] = new byte[100]; + int result = 0; + Support_Resources.copyFile(resources, "GZIPInputStream", + "hyts_gInput.txt.gz"); + String resPath = resources.toString(); + if (resPath.charAt(0) == '/' || resPath.charAt(0) == '\\') { + resPath = resPath.substring(1); + } + final URL gInput = new URL("file:/" + resPath + + "/GZIPInputStream/hyts_gInput.txt.gz"); + TestGZIPInputStream inGZIP = new TestGZIPInputStream(gInput + .openConnection().getInputStream()); + while (!(inGZIP.endofInput())) { + result += inGZIP.read(outBuf, result, outBuf.length - result); + } + assertEquals( + "the checkSum value of the compressed and decompressed data does not equal", + 2074883667L, inGZIP.getChecksum().getValue()); + for (int i = 0; i < orgBuf.length; i++) { + assertTrue( + "the decompressed data does not equal the original data decompressed", + orgBuf[i] == outBuf[i]); + // System.out.println(orgBuf[i] + " " + outBuf[i]); + } + int r = 0; + try { + inGZIP.read(outBuf, 100, 1); + } catch (IndexOutOfBoundsException e) { + r = 1; + } + inGZIP.close(); + // line below fails on RI also, comment out. + // assertEquals("Boundary Check was not present", 1, r); + + // Create compressed data which is exactly 512 bytes (after the + // header), + // the size of the InflaterStream internal buffer + byte[] test = new byte[507]; + for (int i = 0; i < 256; i++) { + test[i] = (byte) i; + } + for (int i = 256; i < test.length; i++) { + test[i] = (byte) (256 - i); + } + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + GZIPOutputStream out = new GZIPOutputStream(bout); + out.write(test); + out.close(); + byte[] comp = bout.toByteArray(); + GZIPInputStream gin2 = new GZIPInputStream(new ByteArrayInputStream( + comp), 512); + int total = 0; + while ((result = gin2.read(test)) != -1) { + total += result; + } + assertEquals("Should return -1", -1, gin2.read()); + gin2.close(); + assertTrue("Incorrectly decompressed", total == test.length); + + gin2 = new GZIPInputStream(new ByteArrayInputStream(comp), 512); + total = 0; + while ((result = gin2.read(new byte[200])) != -1) { + total += result; + } + assertEquals("Should return -1", -1, gin2.read()); + gin2.close(); + assertTrue("Incorrectly decompressed", total == test.length); + + gin2 = new GZIPInputStream(new ByteArrayInputStream(comp), 516); + total = 0; + while ((result = gin2.read(new byte[200])) != -1) { + total += result; + } + assertEquals("Should return -1", -1, gin2.read()); + gin2.close(); + assertTrue("Incorrectly decompressed", total == test.length); + + comp[40] = 0; + gin2 = new GZIPInputStream(new ByteArrayInputStream(comp), 512); + boolean exception = false; + try { + while (gin2.read(test) != -1) { + ; + } + } catch (IOException e) { + exception = true; + } + assertTrue("Exception expected", exception); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + GZIPOutputStream zipout = new GZIPOutputStream(baos); + zipout.write(test); + zipout.close(); + outBuf = new byte[530]; + GZIPInputStream in = new GZIPInputStream(new ByteArrayInputStream(baos + .toByteArray())); + try { + in.read(outBuf, 530, 1); + fail("Test failed IOOBE was not thrown"); + } catch (IndexOutOfBoundsException e) { + } + while (true) { + result = in.read(outBuf, 0, 5); + if (result == -1) { + // "EOF was reached"; + break; + } + } + result = -10; + result = in.read(null, 100, 1); + result = in.read(outBuf, -100, 1); + result = in.read(outBuf, -1, 1);// 100, 1); + } + + /** + * @tests java.util.zip.GZIPInputStream#close() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "close", + args = {} + ) + public void test_close() { + // test method java.util.zip.GZIPInputStream.close + byte outBuf[] = new byte[100]; + try { + int result = 0; + Support_Resources.copyFile(resources, "GZIPInputStream", + "hyts_gInput.txt.gz"); + String resPath = resources.toString(); + if (resPath.charAt(0) == '/' || resPath.charAt(0) == '\\') { + resPath = resPath.substring(1); + } + final URL gInput = new URL("file:/" + resPath + + "/GZIPInputStream/hyts_gInput.txt.gz"); + TestGZIPInputStream inGZIP = new TestGZIPInputStream(gInput + .openConnection().getInputStream()); + while (!(inGZIP.endofInput())) { + result += inGZIP.read(outBuf, result, outBuf.length - result); + } + assertEquals( + "the checkSum value of the compressed and decompressed data does not equal", + 2074883667L, inGZIP.getChecksum().getValue()); + inGZIP.close(); + int r = 0; + try { + inGZIP.read(outBuf, 0, 1); + } catch (IOException e) { + r = 1; + } + assertEquals( + "GZIPInputStream can still be used after close is called", + 1, r); + } catch (IOException e) { + e.printStackTrace(); + fail("unexpected: " + e); + } + } + + @Override + protected void setUp() { + resources = Support_Resources.createTempFolder(); + } + + @Override + protected void tearDown() { + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/GZIPOutputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/GZIPOutputStreamTest.java new file mode 100644 index 0000000..298f63a --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/GZIPOutputStreamTest.java @@ -0,0 +1,238 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.zip.Checksum; +import java.util.zip.GZIPOutputStream; + +@TestTargetClass(GZIPOutputStream.class) +public class GZIPOutputStreamTest extends junit.framework.TestCase { + + class TestGZIPOutputStream extends GZIPOutputStream { + TestGZIPOutputStream(OutputStream out) throws IOException { + super(out); + } + + TestGZIPOutputStream(OutputStream out, int size) throws IOException { + super(out, size); + } + + Checksum getChecksum() { + return crc; + } + } + + /** + * @tests java.util.zip.GZIPOutputStream#GZIPOutputStream(java.io.OutputStream) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "GZIPOutputStream", + args = {java.io.OutputStream.class} + ) + public void test_ConstructorLjava_io_OutputStream() { + try { + FileOutputStream outFile = new FileOutputStream("GZIPOutCon.txt"); + TestGZIPOutputStream outGZIP = new TestGZIPOutputStream(outFile); + assertNotNull("the constructor for GZIPOutputStream is null", + outGZIP); + assertEquals("the CRC value of the outputStream is not zero", 0, + outGZIP.getChecksum().getValue()); + outGZIP.close(); + } catch (IOException e) { + fail("an IO error occured while trying to find the output file or creating GZIP constructor"); + } + } + + /** + * @tests java.util.zip.GZIPOutputStream#GZIPOutputStream(java.io.OutputStream, + * int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "GZIPOutputStream", + args = {java.io.OutputStream.class, int.class} + ) + public void test_ConstructorLjava_io_OutputStreamI() { + try { + FileOutputStream outFile = new FileOutputStream("GZIPOutCon.txt"); + TestGZIPOutputStream outGZIP = new TestGZIPOutputStream(outFile, + 100); + assertNotNull("the constructor for GZIPOutputStream is null", + outGZIP); + assertEquals("the CRC value of the outputStream is not zero", 0, + outGZIP.getChecksum().getValue()); + outGZIP.close(); + } catch (IOException e) { + fail("an IO error occured while trying to find the output file or creating GZIP constructor"); + } + } + + /** + * @tests java.util.zip.GZIPOutputStream#finish() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "finish", + args = {} + ) + public void test_finish() { + // test method java.util.zip.GZIPOutputStream.finish() + byte byteArray[] = {3, 5, 2, 'r', 'g', 'e', 'f', 'd', 'e', 'w'}; + TestGZIPOutputStream outGZIP = null; + FileOutputStream outFile = null; + try { + outFile = new FileOutputStream("GZIPOutFinish.txt"); + outGZIP = new TestGZIPOutputStream(outFile); + + outGZIP.finish(); + int r = 0; + try { + outGZIP.write(byteArray, 0, 1); + } catch (IOException e) { + r = 1; + } + + assertEquals( + "GZIP instance can still be used after finish is called", + 1, r); + outGZIP.close(); + } catch (IOException e) { + fail("an IO error occured while trying to find the output file or creating GZIP constructor"); + } + try { + outFile = new FileOutputStream("GZIPOutFinish.txt"); + outGZIP = new TestGZIPOutputStream(outFile); + outFile.close(); + + outGZIP.finish(); + fail("Expected IOException"); + } catch (IOException e) { + // expected + } + } + + /** + * @tests java.util.zip.GZIPOutputStream#close() + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException checking missed.", + method = "close", + args = {} + ) + public void test_close() { + // test method java.util.zip.GZIPOutputStream.close() + byte byteArray[] = {3, 5, 2, 'r', 'g', 'e', 'f', 'd', 'e', 'w'}; + try { + FileOutputStream outFile = new FileOutputStream("GZIPOutClose2.txt"); + TestGZIPOutputStream outGZIP = new TestGZIPOutputStream(outFile); + outGZIP.close(); + int r = 0; + try { + outGZIP.write(byteArray, 0, 1); + } catch (IOException e) { + r = 1; + } + assertEquals( + "GZIP instance can still be used after close is called", 1, + r); + } catch (IOException e) { + fail("an IO error occured while trying to find the output file or creating GZIP constructor"); + } + } + + /** + * @tests java.util.zip.GZIPOutputStream#write(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "write", + args = {byte[].class, int.class, int.class} + ) + public void test_write$BII() { + // test method java.util.zip.GZIPOutputStream.writeBII + byte byteArray[] = {3, 5, 2, 'r', 'g', 'e', 'f', 'd', 'e', 'w'}; + TestGZIPOutputStream outGZIP = null; + try { + FileOutputStream outFile = new FileOutputStream("GZIPOutWrite.txt"); + outGZIP = new TestGZIPOutputStream(outFile); + outGZIP.write(byteArray, 0, 10); + // ran JDK and found this CRC32 value is 3097700292 + // System.out.print(outGZIP.getChecksum().getValue()); + assertEquals( + "the checksum value was incorrect result of write from GZIP", + 3097700292L, outGZIP.getChecksum().getValue()); + + // test for boundary check + int r = 0; + try { + outGZIP.write(byteArray, 0, 11); + } catch (ArrayIndexOutOfBoundsException e) { + r = 1; + } catch (IndexOutOfBoundsException ee) { + r = 1; + } + assertEquals("out of bounds exception is not present", 1, r); + outGZIP.close(); + } catch (IOException e) { + fail("an IO error occured while trying to find the output file or creating GZIP constructor"); + } + try { + outGZIP.write(byteArray, 0, 10); + fail("Expected IOException"); + } catch (IOException e) { + // expected + } + } + + @Override + protected void setUp() { + } + + @Override + protected void tearDown() { + + try { + File dFile = new File("GZIPOutCon.txt"); + dFile.delete(); + File dFile2 = new File("GZIPOutFinish.txt"); + dFile2.delete(); + File dFile3 = new File("GZIPOutWrite.txt"); + dFile3.delete(); + File dFile4 = new File("GZIPOutClose2.txt"); + dFile4.delete(); + } catch (SecurityException e) { + fail("Cannot delete file for security reasons"); + } + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/InflaterInputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/InflaterInputStreamTest.java new file mode 100644 index 0000000..2e91510 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/InflaterInputStreamTest.java @@ -0,0 +1,514 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargets; + +import junit.framework.TestCase; + +import tests.support.resource.Support_Resources; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; +import java.util.zip.ZipException; + +@TestTargetClass(InflaterInputStream.class) +public class InflaterInputStreamTest extends TestCase { + + // files hyts_constru(O),hyts_constru(OD),hyts_constru(ODI) needs to be + // included as resources + byte outPutBuf[] = new byte[500]; + + class MyInflaterInputStream extends InflaterInputStream { + MyInflaterInputStream(InputStream in) { + super(in); + } + + MyInflaterInputStream(InputStream in, Inflater infl) { + super(in, infl); + } + + MyInflaterInputStream(InputStream in, Inflater infl, int size) { + super(in, infl, size); + } + + void myFill() throws IOException { + fill(); + } + } + + /** + * @tests java.util.zip.InflaterInputStream#InflaterInputStream(java.io.InputStream) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "InflaterInputStream", + args = {java.io.InputStream.class} + ) + public void test_ConstructorLjava_io_InputStream() throws IOException { + byte byteArray[] = new byte[100]; + InputStream infile = Support_Resources.getStream("hyts_constru_OD.txt"); + InflaterInputStream inflatIP = new InflaterInputStream(infile); + + inflatIP.read(byteArray, 0, 5);// only suppose to read in 5 bytes + inflatIP.close(); + } + + /** + * @tests java.util.zip.InflaterInputStream#InflaterInputStream(java.io.InputStream, + * java.util.zip.Inflater) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "InflaterInputStream", + args = {java.io.InputStream.class, java.util.zip.Inflater.class} + ) + public void test_ConstructorLjava_io_InputStreamLjava_util_zip_Inflater() + throws IOException { + byte byteArray[] = new byte[100]; + InputStream infile = Support_Resources.getStream("hyts_constru_OD.txt"); + Inflater inflate = new Inflater(); + InflaterInputStream inflatIP = new InflaterInputStream(infile, inflate); + + inflatIP.read(byteArray, 0, 5);// only suppose to read in 5 bytes + inflatIP.close(); + } + + /** + * @tests java.util.zip.InflaterInputStream#InflaterInputStream(java.io.InputStream, + * java.util.zip.Inflater, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "IllegalArgumentException checking missed.", + method = "InflaterInputStream", + args = {java.io.InputStream.class, java.util.zip.Inflater.class, int.class} + ) + public void test_ConstructorLjava_io_InputStreamLjava_util_zip_InflaterI() + throws IOException { + int result = 0; + int buffer[] = new int[500]; + InputStream infile = Support_Resources + .getStream("hyts_constru_ODI.txt"); + Inflater inflate = new Inflater(); + InflaterInputStream inflatIP = new InflaterInputStream(infile, inflate, + 1); + + int i = 0; + while ((result = inflatIP.read()) != -1) { + buffer[i] = result; + i++; + } + inflatIP.close(); + + try { + inflatIP = new InflaterInputStream(infile, inflate, -1); + fail("IllegalArgumentException expected."); + } catch (IllegalArgumentException ee) { + // expected + } + + } + + /** + * @tests java.util.zip.InflaterInputStream#mark(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "mark", + args = {int.class} + ) + public void test_markI() { + InputStream is = new ByteArrayInputStream(new byte[10]); + InflaterInputStream iis = new InflaterInputStream(is); + // mark do nothing, do no check + iis.mark(0); + iis.mark(-1); + iis.mark(10000000); + } + + /** + * @tests java.util.zip.InflaterInputStream#markSupported() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "markSupported", + args = {} + ) + public void test_markSupported() { + InputStream is = new ByteArrayInputStream(new byte[10]); + InflaterInputStream iis = new InflaterInputStream(is); + assertFalse(iis.markSupported()); + assertTrue(is.markSupported()); + } + + /** + * @tests java.util.zip.InflaterInputStream#read() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "read", + args = {} + ) + public void test_read() throws IOException { + int result = 0; + int buffer[] = new int[500]; + byte orgBuffer[] = {1, 3, 4, 7, 8}; + InputStream infile = Support_Resources.getStream("hyts_constru_OD.txt"); + Inflater inflate = new Inflater(); + InflaterInputStream inflatIP = new InflaterInputStream(infile, inflate); + + int i = 0; + while ((result = inflatIP.read()) != -1) { + buffer[i] = result; + i++; + } + inflatIP.close(); + + for (int j = 0; j < orgBuffer.length; j++) { + assertTrue( + "original compressed data did not equal decompressed data", + buffer[j] == orgBuffer[j]); + } + inflatIP.close(); + try { + inflatIP.read(); + fail("IOException expected"); + } catch (IOException ee) { + // expected. + } + } + + /** + * @tests java.util.zip.InflaterInputStream#read(byte[], int, int) + */ + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException & ZipException checking missed. Additional tests for fill method is not needed.", + method = "read", + args = {byte[].class, int.class, int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException & ZipException checking missed. Additional tests for fill method is not needed.", + method = "fill", + args = {} + ) + }) + public void test_read$BII() throws IOException { + byte[] test = new byte[507]; + for (int i = 0; i < 256; i++) { + test[i] = (byte) i; + } + for (int i = 256; i < test.length; i++) { + test[i] = (byte) (256 - i); + } + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + DeflaterOutputStream dos = new DeflaterOutputStream(baos); + dos.write(test); + dos.close(); + InputStream is = new ByteArrayInputStream(baos.toByteArray()); + InflaterInputStream iis = new InflaterInputStream(is); + byte[] outBuf = new byte[530]; + int result = 0; + while (true) { + result = iis.read(outBuf, 0, 5); + if (result == -1) { + // "EOF was reached"; + break; + } + } + try { + iis.read(outBuf, -1, 10); + fail("should throw IOOBE."); + } catch (IndexOutOfBoundsException e) { + // expected; + } + iis.close(); + } + + /** + * @tests java.util.zip.InflaterInputStream#read(byte[], int, int) + */ + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException checking.", + method = "read", + args = {byte[].class, int.class, int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException checking.", + method = "fill", + args = {} + ) + }) + public void test_read$BII2() throws IOException { + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + FileInputStream fis = new FileInputStream(new File(resources, + "Broken_manifest.jar")); + InflaterInputStream iis = new InflaterInputStream(fis); + byte[] outBuf = new byte[530]; + + iis.close(); + try { + iis.read(outBuf, 0, 5); + fail("IOException expected"); + } catch (IOException ee) { + // expected. + } + } + + @TestTargets({ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException checking.", + method = "read", + args = {byte[].class, int.class, int.class} + ), + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException checking.", + method = "fill", + args = {} + ) + }) + public void test_read$BII3() throws IOException { + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + FileInputStream fis = new FileInputStream(new File(resources, + "Broken_manifest.jar")); + InflaterInputStream iis = new InflaterInputStream(fis); + byte[] outBuf = new byte[530]; + + try { + iis.read(); + fail("IOException expected."); + } catch (IOException ee) { + // expected + } + } + + /** + * @tests java.util.zip.InflaterInputStream#reset() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "reset", + args = {} + ) + public void test_reset() { + InputStream is = new ByteArrayInputStream(new byte[10]); + InflaterInputStream iis = new InflaterInputStream(is); + try { + iis.reset(); + fail("Should throw IOException"); + } catch (IOException e) { + // correct + } + } + + /** + * @tests java.util.zip.InflaterInputStream#skip(long) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IOException checking missed.", + method = "skip", + args = {long.class} + ) + public void test_skipJ() throws IOException { + InputStream is = Support_Resources.getStream("hyts_available.tst"); + InflaterInputStream iis = new InflaterInputStream(is); + + // Tests for skipping a negative number of bytes. + try { + iis.skip(-3); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + // Expected + } + assertEquals("Incorrect Byte Returned.", 5, iis.read()); + + try { + iis.skip(Integer.MIN_VALUE); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + // Expected + } + assertEquals("Incorrect Byte Returned.", 4, iis.read()); + + // Test to make sure the correct number of bytes were skipped + assertEquals("Incorrect Number Of Bytes Skipped.", 3, iis.skip(3)); + + // Test to see if the number of bytes skipped returned is true. + assertEquals("Incorrect Byte Returned.", 7, iis.read()); + + assertEquals("Incorrect Number Of Bytes Skipped.", 0, iis.skip(0)); + assertEquals("Incorrect Byte Returned.", 0, iis.read()); + + // Test for skipping more bytes than available in the stream + assertEquals("Incorrect Number Of Bytes Skipped.", 2, iis.skip(4)); + assertEquals("Incorrect Byte Returned.", -1, iis.read()); + iis.close(); + } + + /** + * @tests java.util.zip.InflaterInputStream#skip(long) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "IOException checking missed.", + method = "skip", + args = {long.class} + ) + public void test_skipJ2() throws IOException { + int result = 0; + int buffer[] = new int[100]; + byte orgBuffer[] = {1, 3, 4, 7, 8}; + + // testing for negative input to skip + InputStream infile = Support_Resources.getStream("hyts_constru_OD.txt"); + Inflater inflate = new Inflater(); + InflaterInputStream inflatIP = new InflaterInputStream(infile, inflate, + 10); + long skip; + try { + skip = inflatIP.skip(Integer.MIN_VALUE); + fail("Expected IllegalArgumentException when skip() is called with negative parameter"); + } catch (IllegalArgumentException e) { + // Expected + } + inflatIP.close(); + + // testing for number of bytes greater than input. + InputStream infile2 = Support_Resources + .getStream("hyts_constru_OD.txt"); + InflaterInputStream inflatIP2 = new InflaterInputStream(infile2); + + // looked at how many bytes the skip skipped. It is + // 5 and its supposed to be the entire input stream. + + skip = inflatIP2.skip(Integer.MAX_VALUE); + // System.out.println(skip); + assertEquals("method skip() returned wrong number of bytes skipped", 5, + skip); + + // test for skipping of 2 bytes + InputStream infile3 = Support_Resources + .getStream("hyts_constru_OD.txt"); + InflaterInputStream inflatIP3 = new InflaterInputStream(infile3); + skip = inflatIP3.skip(2); + assertEquals( + "the number of bytes returned by skip did not correspond with its input parameters", + 2, skip); + int i = 0; + result = 0; + while ((result = inflatIP3.read()) != -1) { + buffer[i] = result; + i++; + } + inflatIP2.close(); + + for (int j = 2; j < orgBuffer.length; j++) { + assertTrue( + "original compressed data did not equal decompressed data", + buffer[j - 2] == orgBuffer[j]); + } + + try { + inflatIP2.skip(4); + fail("IOException expected."); + } catch (IOException ee) { + // expected + } + } + + /** + * @tests java.util.zip.InflaterInputStream#available() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "available", + args = {} + ) + public void test_available() throws IOException { + InputStream is = Support_Resources.getStream("hyts_available.tst"); + InflaterInputStream iis = new InflaterInputStream(is); + + int available; + int read; + for (int i = 0; i < 11; i++) { + read = iis.read(); + available = iis.available(); + if (read == -1) { + assertEquals("Bytes Available Should Return 0 ", 0, available); + } else { + assertEquals("Bytes Available Should Return 1.", 1, available); + } + } + + iis.close(); + try { + iis.available(); + fail("available after close should throw IOException."); + } catch (IOException e) { + // Expected + } + } + + /** + * @tests java.util.zip.InflaterInputStream#close() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "IOException can not be tested.", + method = "close", + args = {} + ) + public void test_close() throws IOException { + InflaterInputStream iin = new InflaterInputStream( + new ByteArrayInputStream(new byte[0])); + iin.close(); + + // test for exception + iin.close(); + + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/InflaterTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/InflaterTest.java new file mode 100644 index 0000000..8b89180 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/InflaterTest.java @@ -0,0 +1,1132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.BufferedInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.zip.Adler32; +import java.io.UnsupportedEncodingException; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; +import java.util.zip.ZipException; + +import tests.support.resource.Support_Resources; + +@TestTargetClass(Inflater.class) +public class InflaterTest extends junit.framework.TestCase { + byte outPutBuff1[] = new byte[500]; + + byte outPutDiction[] = new byte[500]; + + /** + * @tests java.util.zip.Inflater#end() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "end", + args = {} + ) + public void test_end() { + // test method of java.util.zip.inflater.end() + byte byteArray[] = {5, 2, 3, 7, 8}; + + int r = 0; + Inflater inflate = new Inflater(); + inflate.setInput(byteArray); + inflate.end(); + try { + inflate.reset(); + inflate.setInput(byteArray); + } catch (NullPointerException e) { + r = 1; + } + assertEquals("inflate can still be used after end is called", 1, r); + + Inflater i = new Inflater(); + i.end(); + // check for exception + i.end(); + } + + /** + * @tests java.util.zip.Inflater#finished() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "finished", + args = {} + ) + public void test_finished() { + // test method of java.util.zip.inflater.finished() + byte byteArray[] = {1, 3, 4, 7, 8, 'e', 'r', 't', 'y', '5'}; + Inflater inflate = new Inflater(false); + byte outPutInf[] = new byte[500]; + try { + while (!(inflate.finished())) { + if (inflate.needsInput()) { + inflate.setInput(outPutBuff1); + } + + inflate.inflate(outPutInf); + } + assertTrue( + "the method finished() returned false when no more data needs to be decompressed", + inflate.finished()); + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } + for (int i = 0; i < byteArray.length; i++) { + assertTrue( + "Final decompressed data does not equal the original data", + byteArray[i] == outPutInf[i]); + } + assertEquals( + "final decompressed data contained more bytes than original - finished()", + 0, outPutInf[byteArray.length]); + } + + /** + * @tests java.util.zip.Inflater#getAdler() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getAdler", + args = {} + ) + public void test_getAdler() { + // test method of java.util.zip.inflater.getAdler() + byte dictionaryArray[] = {'e', 'r', 't', 'a', 'b', 2, 3}; + + Inflater inflateDiction = new Inflater(); + inflateDiction.setInput(outPutDiction); + if (inflateDiction.needsDictionary() == true) { + // getting the checkSum value through the Adler32 class + Adler32 adl = new Adler32(); + adl.update(dictionaryArray); + long checkSumR = adl.getValue(); + assertTrue( + "the checksum value returned by getAdler() is not the same as the checksum returned by creating the adler32 instance", + checkSumR == inflateDiction.getAdler()); + } + } + + /** + * @tests java.util.zip.Inflater#getRemaining() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getRemaining", + args = {} + ) + public void test_getRemaining() { + // test method of java.util.zip.inflater.getRemaining() + byte byteArray[] = {1, 3, 5, 6, 7}; + Inflater inflate = new Inflater(); + assertEquals( + "upon creating an instance of inflate, getRemaining returned a non zero value", + 0, inflate.getRemaining()); + inflate.setInput(byteArray); + assertTrue( + "getRemaining returned zero when there is input in the input buffer", + inflate.getRemaining() != 0); + } + + /** + * @tests java.util.zip.Inflater#getTotalIn() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getTotalIn", + args = {} + ) + public void test_getTotalIn() { + // test method of java.util.zip.inflater.getTotalIn() + // creating the decompressed data + byte outPutBuf[] = new byte[500]; + byte byteArray[] = {1, 3, 4, 7, 8}; + byte outPutInf[] = new byte[500]; + int x = 0; + Deflater deflate = new Deflater(1); + deflate.setInput(byteArray); + while (!(deflate.needsInput())) { + x += deflate.deflate(outPutBuf, x, outPutBuf.length - x); + } + deflate.finish(); + while (!(deflate.finished())) { + x = x + deflate.deflate(outPutBuf, x, outPutBuf.length - x); + } + + Inflater inflate = new Inflater(); + try { + while (!(inflate.finished())) { + if (inflate.needsInput()) { + inflate.setInput(outPutBuf); + } + + inflate.inflate(outPutInf); + } + } catch (DataFormatException e) { + fail("Input to inflate is invalid or corrupted - getTotalIn"); + } + // System.out.print(deflate.getTotalOut() + " " + inflate.getTotalIn()); + assertTrue( + "the total byte in outPutBuf did not equal the byte returned in getTotalIn", + inflate.getTotalIn() == deflate.getTotalOut()); + + Inflater inflate2 = new Inflater(); + int offSet = 0;// seems only can start as 0 + int length = 4; + try { + // seems no while loops allowed + if (inflate2.needsInput()) { + inflate2.setInput(outPutBuff1, offSet, length); + } + + inflate2.inflate(outPutInf); + + } catch (DataFormatException e) { + fail("Input to inflate is invalid or corrupted - getTotalIn"); + } + // System.out.print(inflate2.getTotalIn() + " " + length); + assertTrue( + "total byte dictated by length did not equal byte returned in getTotalIn", + inflate2.getTotalIn() == length); + } + + /** + * @tests java.util.zip.Inflater#getTotalOut() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getTotalOut", + args = {} + ) + public void test_getTotalOut() { + // test method of java.util.zip.inflater.Inflater() + // creating the decompressed data + byte outPutBuf[] = new byte[500]; + byte byteArray[] = {1, 3, 4, 7, 8}; + int y = 0; + int x = 0; + Deflater deflate = new Deflater(1); + deflate.setInput(byteArray); + while (!(deflate.needsInput())) { + x += deflate.deflate(outPutBuf, x, outPutBuf.length - x); + } + deflate.finish(); + while (!(deflate.finished())) { + x = x + deflate.deflate(outPutBuf, x, outPutBuf.length - x); + } + + Inflater inflate = new Inflater(); + byte outPutInf[] = new byte[500]; + try { + while (!(inflate.finished())) { + if (inflate.needsInput()) { + inflate.setInput(outPutBuf); + } + + y += inflate.inflate(outPutInf); + } + } catch (DataFormatException e) { + fail("Input to inflate is invalid or corrupted - getTotalIn"); + } + + assertTrue( + "the sum of the bytes returned from inflate does not equal the bytes of getTotalOut()", + y == inflate.getTotalOut()); + assertTrue( + "the total number of bytes to be compressed does not equal the total bytes decompressed", + inflate.getTotalOut() == deflate.getTotalIn()); + + // testing inflate(byte,int,int) + inflate.reset(); + y = 0; + int offSet = 0;// seems only can start as 0 + int length = 4; + try { + while (!(inflate.finished())) { + if (inflate.needsInput()) { + inflate.setInput(outPutBuf); + } + + y += inflate.inflate(outPutInf, offSet, length); + } + } catch (DataFormatException e) { + System.out + .println("Input to inflate is invalid or corrupted - getTotalIn"); + } + assertTrue( + "the sum of the bytes returned from inflate does not equal the bytes of getTotalOut()", + y == inflate.getTotalOut()); + assertTrue( + "the total number of bytes to be compressed does not equal the total bytes decompressed", + inflate.getTotalOut() == deflate.getTotalIn()); + } + + /** + * @tests java.util.zip.Inflater#inflate(byte[]) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "DataFormatException checking missed.", + method = "inflate", + args = {byte[].class} + ) + public void test_inflate$B() { + // test method of java.util.zip.inflater.inflate(byte) + + byte byteArray[] = {1, 3, 4, 7, 8, 'e', 'r', 't', 'y', '5'}; + byte outPutInf[] = new byte[500]; + Inflater inflate = new Inflater(); + try { + while (!(inflate.finished())) { + if (inflate.needsInput()) { + inflate.setInput(outPutBuff1); + } + inflate.inflate(outPutInf); + } + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } + for (int i = 0; i < byteArray.length; i++) { + assertTrue( + "Final decompressed data does not equal the original data", + byteArray[i] == outPutInf[i]); + } + assertEquals( + "final decompressed data contained more bytes than original - inflateB", + 0, outPutInf[byteArray.length]); + // testing for an empty input array + byte outPutBuf[] = new byte[500]; + byte emptyArray[] = new byte[11]; + int x = 0; + Deflater defEmpty = new Deflater(3); + defEmpty.setInput(emptyArray); + while (!(defEmpty.needsInput())) { + x += defEmpty.deflate(outPutBuf, x, outPutBuf.length - x); + } + defEmpty.finish(); + while (!(defEmpty.finished())) { + x += defEmpty.deflate(outPutBuf, x, outPutBuf.length - x); + } + assertTrue( + "the total number of byte from deflate did not equal getTotalOut - inflate(byte)", + x == defEmpty.getTotalOut()); + assertTrue( + "the number of input byte from the array did not correspond with getTotalIn - inflate(byte)", + defEmpty.getTotalIn() == emptyArray.length); + Inflater infEmpty = new Inflater(); + try { + while (!(infEmpty.finished())) { + if (infEmpty.needsInput()) { + infEmpty.setInput(outPutBuf); + } + infEmpty.inflate(outPutInf); + } + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } + for (int i = 0; i < emptyArray.length; i++) { + assertTrue( + "Final decompressed data does not equal the original data", + emptyArray[i] == outPutInf[i]); + assertEquals("Final decompressed data does not equal zero", 0, + outPutInf[i]); + } + assertEquals( + "Final decompressed data contains more element than original data", + 0, outPutInf[emptyArray.length]); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "inflate", + args = {byte[].class} + ) + public void test_inflate$B1() { + byte codedData[] = { + 120, -38, 75, -54, 73, -52, 80, 40, 46, 41, -54, -52, 75, 87, + 72, -50, -49, 43, 73, -52, -52, 43, 86, 72, 2, 10, 34, 99, + -123, -60, -68, 20, -80, 32, 0, -101, -69, 17, 84}; + String codedString = "blah string contains blahblahblahblah and blah"; + + Inflater infl1 = new Inflater(); + Inflater infl2 = new Inflater(); + + byte[] result = new byte[100]; + int decLen = 0; + + infl1.setInput(codedData, 0, codedData.length); + try { + decLen = infl1.inflate(result); + } catch (DataFormatException e) { + fail("Unexpected DataFormatException"); + } + + infl1.end(); + assertEquals(codedString, new String(result, 0, decLen)); + codedData[5] = 0; + + infl2.setInput(codedData, 0, codedData.length); + try { + decLen = infl2.inflate(result); + fail("Expected DataFormatException"); + } catch (DataFormatException e) { + // expected + } + + infl2.end(); + } + + /** + * @tests java.util.zip.Inflater#inflate(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "DataFormatException checking missed.", + method = "inflate", + args = {byte[].class, int.class, int.class} + ) + public void test_inflate$BII() { + // test method of java.util.zip.inflater.inflate(byte,int,int) + + byte byteArray[] = {1, 3, 4, 7, 8, 'e', 'r', 't', 'y', '5'}; + byte outPutInf[] = new byte[100]; + int y = 0; + Inflater inflate = new Inflater(); + try { + while (!(inflate.finished())) { + if (inflate.needsInput()) { + inflate.setInput(outPutBuff1); + } + y += inflate.inflate(outPutInf, y, outPutInf.length - y); + } + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } + for (int i = 0; i < byteArray.length; i++) { + assertTrue( + "Final decompressed data does not equal the original data", + byteArray[i] == outPutInf[i]); + } + assertEquals( + "final decompressed data contained more bytes than original - inflateB", + 0, outPutInf[byteArray.length]); + + // test boundary checks + inflate.reset(); + int r = 0; + int offSet = 0; + int lengthError = 101; + try { + if (inflate.needsInput()) { + inflate.setInput(outPutBuff1); + } + inflate.inflate(outPutInf, offSet, lengthError); + + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } catch (ArrayIndexOutOfBoundsException e) { + r = 1; + } + assertEquals("out of bounds error did not get caught", 1, r); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "inflate", + args = {byte[].class, int.class, int.class} + ) + public void test_inflate$BII1() { + byte codedData[] = { + 120, -38, 75, -54, 73, -52, 80, 40, 46, 41, -54, -52, 75, 87, + 72, -50, -49, 43, 73, -52, -52, 43, 86, 72, 2, 10, 34, 99, + -123, -60, -68, 20, -80, 32, 0, -101, -69, 17, 84}; + String codedString = "blah string"; + + Inflater infl1 = new Inflater(); + Inflater infl2 = new Inflater(); + + byte[] result = new byte[100]; + int decLen = 0; + + infl1.setInput(codedData, 0, codedData.length); + try { + decLen = infl1.inflate(result, 10, 11); + } catch (DataFormatException e) { + fail("Unexpected DataFormatException"); + } + + infl1.end(); + assertEquals(codedString, new String(result, 10, decLen)); + codedData[5] = 0; + + infl2.setInput(codedData, 0, codedData.length); + try { + decLen = infl2.inflate(result, 10, 11); + fail("Expected DataFormatException"); + } catch (DataFormatException e) { + // expected + } + + infl2.end(); + } + + /** + * @tests java.util.zip.Inflater#Inflater() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Inflater", + args = {} + ) + public void test_Constructor() { + // test method of java.util.zip.inflater.Inflater() + try { + Inflater inflate = new Inflater(); + assertNotNull("failed to create the instance of inflater", inflate); + + } catch (Exception e) { + + assertTrue("Inflate () constructor threw an exception", true); + } + } + + /** + * @tests java.util.zip.Inflater#Inflater(boolean) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "Inflater", + args = {boolean.class} + ) + public void test_ConstructorZ() { + // test method of java.util.zip.inflater.Inflater(boolean) + // note does not throw exception if deflater has a header, but inflater + // doesn't or vice versa. + byte byteArray[] = {1, 3, 4, 7, 8, 'e', 'r', 't', 'y', '5'}; + Inflater inflate = new Inflater(true); + assertNotNull("failed to create the instance of inflater", inflate); + byte outPutInf[] = new byte[500]; + int r = 0; + try { + while (!(inflate.finished())) { + if (inflate.needsInput()) { + inflate.setInput(outPutBuff1); + } + + inflate.inflate(outPutInf); + } + for (int i = 0; i < byteArray.length; i++) { + assertEquals( + "the output array from inflate should contain 0 because the header of inflate and deflate did not match, but this failed", + 0, outPutBuff1[i]); + } + } catch (DataFormatException e) { + r = 1; + } + assertEquals( + "Error: exception should be thrown because of header inconsistency", + 1, r); + + } + + /** + * @tests java.util.zip.Inflater#needsDictionary() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "needsDictionary", + args = {} + ) + public void test_needsDictionary() { + // test method of java.util.zip.inflater.needsDictionary() + // note: this flag is set after inflate is called + byte outPutInf[] = new byte[500]; + + // testing with dictionary set. + Inflater inflateDiction = new Inflater(); + if (inflateDiction.needsInput()) { + inflateDiction.setInput(outPutDiction); + } + try { + assertEquals("should return 0 because needs dictionary", 0, + inflateDiction.inflate(outPutInf)); + } catch (DataFormatException e) { + fail("Should not cause exception"); + } + assertTrue( + "method needsDictionary returned false when dictionary was used in deflater", + inflateDiction.needsDictionary()); + + // testing without dictionary + Inflater inflate = new Inflater(); + try { + inflate.setInput(outPutBuff1); + inflate.inflate(outPutInf); + assertFalse( + "method needsDictionary returned true when dictionary was not used in deflater", + inflate.needsDictionary()); + } catch (DataFormatException e) { + fail("Input to inflate is invalid or corrupted - needsDictionary"); + } + + // Regression test for HARMONY-86 + Inflater inf = new Inflater(); + assertFalse(inf.needsDictionary()); + assertEquals(0, inf.getTotalIn()); + assertEquals(0, inf.getTotalOut()); + assertEquals(0, inf.getBytesRead()); + assertEquals(0, inf.getBytesWritten()); + } + + /** + * @tests java.util.zip.Inflater#needsInput() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "needsInput", + args = {} + ) + public void test_needsInput() { + // test method of java.util.zip.inflater.needsInput() + Inflater inflate = new Inflater(); + assertTrue( + "needsInput give the wrong boolean value as a result of no input buffer", + inflate.needsInput()); + + byte byteArray[] = {2, 3, 4, 't', 'y', 'u', 'e', 'w', 7, 6, 5, 9}; + inflate.setInput(byteArray); + assertFalse( + "methodNeedsInput returned true when the input buffer is full", + inflate.needsInput()); + + inflate.reset(); + byte byteArrayEmpty[] = new byte[0]; + inflate.setInput(byteArrayEmpty); + assertTrue( + "needsInput give wrong boolean value as a result of an empty input buffer", + inflate.needsInput()); + } + + /** + * @tests java.util.zip.Inflater#reset() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "reset", + args = {} + ) + public void test_reset() { + // test method of java.util.zip.inflater.reset() + byte byteArray[] = {1, 3, 4, 7, 8, 'e', 'r', 't', 'y', '5'}; + byte outPutInf[] = new byte[100]; + int y = 0; + Inflater inflate = new Inflater(); + try { + while (!(inflate.finished())) { + if (inflate.needsInput()) { + inflate.setInput(outPutBuff1); + } + y += inflate.inflate(outPutInf, y, outPutInf.length - y); + } + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } + for (int i = 0; i < byteArray.length; i++) { + assertTrue( + "Final decompressed data does not equal the original data", + byteArray[i] == outPutInf[i]); + } + assertEquals( + "final decompressed data contained more bytes than original - reset", + 0, outPutInf[byteArray.length]); + + // testing that resetting the inflater will also return the correct + // decompressed data + + inflate.reset(); + try { + while (!(inflate.finished())) { + if (inflate.needsInput()) { + inflate.setInput(outPutBuff1); + } + inflate.inflate(outPutInf); + } + } catch (DataFormatException e) { + fail("Invalid input to be decompressed"); + } + for (int i = 0; i < byteArray.length; i++) { + assertTrue( + "Final decompressed data does not equal the original data", + byteArray[i] == outPutInf[i]); + } + assertEquals( + "final decompressed data contained more bytes than original - reset", + 0, outPutInf[byteArray.length]); + + } + + + /** + * @tests java.util.zip.Inflater#setInput(byte[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setInput", + args = {byte[].class} + ) + public void test_setInput$B() { + // test method of java.util.zip.inflater.setInput(byte) + byte byteArray[] = {2, 3, 4, 't', 'y', 'u', 'e', 'w', 7, 6, 5, 9}; + Inflater inflate = new Inflater(); + inflate.setInput(byteArray); + assertTrue("setInputB did not deliver any byte to the input buffer", + inflate.getRemaining() != 0); + } + + /** + * @tests java.util.zip.Inflater#setInput(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setInput", + args = {byte[].class, int.class, int.class} + ) + public void test_setInput$BII() { + // test method of java.util.zip.inflater.setInput(byte,int,int) + byte byteArray[] = {2, 3, 4, 't', 'y', 'u', 'e', 'w', 7, 6, 5, 9}; + int offSet = 6; + int length = 6; + Inflater inflate = new Inflater(); + inflate.setInput(byteArray, offSet, length); + assertTrue( + "setInputBII did not deliver the right number of bytes to the input buffer", + inflate.getRemaining() == length); + // boundary check + inflate.reset(); + int r = 0; + try { + inflate.setInput(byteArray, 100, 100); + } catch (ArrayIndexOutOfBoundsException e) { + r = 1; + } + assertEquals("boundary check is not present for setInput", 1, r); + } + + @Override + protected void setUp() { + try { + java.io.InputStream infile = Support_Resources + .getStream("hyts_compressD.txt"); + BufferedInputStream inflatIP = new BufferedInputStream(infile); + inflatIP.read(outPutBuff1, 0, outPutBuff1.length); + inflatIP.close(); + + java.io.InputStream infile2 = Support_Resources + .getStream("hyts_compDiction.txt"); + BufferedInputStream inflatIP2 = new BufferedInputStream(infile2); + inflatIP2.read(outPutDiction, 0, outPutDiction.length); + inflatIP2.close(); + + } catch (FileNotFoundException e) { + fail("input file to test InflaterInputStream constructor is not found"); + } catch (ZipException e) { + fail("read() threw an zip exception while testing constructor"); + } catch (IOException e) { + fail("read() threw an exception while testing constructor"); + } + } + + @Override + protected void tearDown() { + } + + /** + * @tests java.util.zip.Deflater#getBytesRead() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getBytesRead", + args = {} + ) + public void test_getBytesRead() throws DataFormatException, + UnsupportedEncodingException { + // Regression test for HARMONY-158 + Deflater def = new Deflater(); + Inflater inf = new Inflater(); + assertEquals(0, def.getTotalIn()); + assertEquals(0, def.getTotalOut()); + assertEquals(0, def.getBytesRead()); + // Encode a String into bytes + String inputString = "blahblahblah??"; + byte[] input = inputString.getBytes("UTF-8"); + + // Compress the bytes + byte[] output = new byte[100]; + def.setInput(input); + def.finish(); + def.deflate(output); + inf.setInput(output); + int compressedDataLength = inf.inflate(input); + assertEquals(16, inf.getTotalIn()); + assertEquals(compressedDataLength, inf.getTotalOut()); + assertEquals(16, inf.getBytesRead()); + } + + /** + * @tests java.util.zip.Deflater#getBytesRead() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getBytesWritten", + args = {} + ) + public void test_getBytesWritten() throws DataFormatException, + UnsupportedEncodingException { + // Regression test for HARMONY-158 + Deflater def = new Deflater(); + Inflater inf = new Inflater(); + assertEquals(0, def.getTotalIn()); + assertEquals(0, def.getTotalOut()); + assertEquals(0, def.getBytesWritten()); + // Encode a String into bytes + String inputString = "blahblahblah??"; + byte[] input = inputString.getBytes("UTF-8"); + + // Compress the bytes + byte[] output = new byte[100]; + def.setInput(input); + def.finish(); + def.deflate(output); + inf.setInput(output); + int compressedDataLength = inf.inflate(input); + assertEquals(16, inf.getTotalIn()); + assertEquals(compressedDataLength, inf.getTotalOut()); + assertEquals(14, inf.getBytesWritten()); + } + + /** + * @tests java.util.zip.Deflater#inflate(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Regression test", + method = "inflate", + args = {byte[].class, int.class, int.class} + ) + public void testInflate() throws Exception { + // Regression for HARMONY-81 + Inflater inf = new Inflater(); + int res = inf.inflate(new byte[0], 0, 0); + + assertEquals(0, res); + + // Regression for HARMONY-2508 + Inflater inflater = new Inflater(); + byte[] b = new byte[1024]; + assertEquals(0, inflater.inflate(b)); + inflater.end(); + + // Regression for HARMONY-2510 + inflater = new Inflater(); + byte[] input = new byte[] {-1}; + inflater.setInput(input); + try { + inflater.inflate(b); + fail("should throw DataFormateException"); + } catch (DataFormatException e) { + // expected + } + } + + /** + * @tests java.util.zip.Deflater#inflate(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Can not be checked. Should be tested in DX test package.", + method = "finalize", + args = {} + ) + public void testFinalize() throws Exception { + } + + /** + * @tests java.util.zip.Inflater#setDictionary(byte[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Checks setDictionary. Can be splitted for 2 separate tests for Deflater & Inflater. Can be used as a base for setDictionary with additional params.", + method = "setDictionary", + args = {byte[].class} + ) + public void testsetDictionary$B() throws Exception { + int i = 0; + String inputString = "blah string contains blahblahblahblah and blah"; + String dictionary1 = "blah"; + String dictionary2 = "1234"; + + byte[] outputNo = new byte[100]; + byte[] output1 = new byte[100]; + byte[] output2 = new byte[100]; + Deflater defDictNo = new Deflater(9); + Deflater defDict1 = new Deflater(9); + Deflater defDict2 = new Deflater(9); + + defDict1.setDictionary(dictionary1.getBytes()); + defDict2.setDictionary(dictionary2.getBytes()); + + defDictNo.setInput(inputString.getBytes()); + defDict1.setInput(inputString.getBytes()); + defDict2.setInput(inputString.getBytes()); + + defDictNo.finish(); + defDict1.finish(); + defDict2.finish(); + + int dataLenNo = defDictNo.deflate(outputNo); + int dataLen1 = defDict1.deflate(output1); + int dataLen2 = defDict2.deflate(output2); + + boolean passNo1 = false; + boolean passNo2 = false; + boolean pass12 = false; + + for (i = 0; i < (dataLenNo < dataLen1 ? dataLenNo : dataLen1); i++) { + if (outputNo[i] != output1[i]) { + passNo1 = true; + break; + } + } + for (i = 0; i < (dataLenNo < dataLen1 ? dataLenNo : dataLen2); i++) { + if (outputNo[i] != output2[i]) { + passNo2 = true; + break; + } + } + for (i = 0; i < (dataLen1 < dataLen2 ? dataLen1 : dataLen2); i++) { + if (output1[i] != output2[i]) { + pass12 = true; + break; + } + } + + assertTrue( + "Compressed data the same for stream with dictionary and without it.", + passNo1); + assertTrue( + "Compressed data the same for stream with dictionary and without it.", + passNo2); + assertTrue( + "Compressed data the same for stream with different dictionaries.", + pass12); + + Inflater inflNo = new Inflater(); + Inflater infl1 = new Inflater(); + Inflater infl2 = new Inflater(); + + byte[] result = new byte[100]; + int decLen; + + inflNo.setInput(outputNo, 0, dataLenNo); + decLen = inflNo.inflate(result); + + assertFalse(inflNo.needsDictionary()); + inflNo.end(); + assertEquals(inputString, new String(result, 0, decLen)); + + infl1.setInput(output1, 0, dataLen1); + decLen = infl1.inflate(result); + + assertTrue(infl1.needsDictionary()); + infl1.setDictionary(dictionary1.getBytes()); + decLen = infl1.inflate(result); + infl1.end(); + assertEquals(inputString, new String(result, 0, decLen)); + + infl2.setInput(output2, 0, dataLen2); + decLen = infl2.inflate(result); + + assertTrue(infl2.needsDictionary()); + infl2.setDictionary(dictionary2.getBytes()); + decLen = infl2.inflate(result); + infl2.end(); + assertEquals(inputString, new String(result, 0, decLen)); + + + inflNo = new Inflater(); + infl1 = new Inflater(); + inflNo.setInput(outputNo, 0, dataLenNo); + try { + infl1.setDictionary(dictionary1.getBytes()); + fail("IllegalArgumentException expected."); + } catch (IllegalArgumentException ee) { + // expected. + } + inflNo.end(); + + infl1.setInput(output1, 0, dataLen1); + decLen = infl1.inflate(result); + + assertTrue(infl1.needsDictionary()); + try { + infl1.setDictionary(dictionary2.getBytes()); + fail("IllegalArgumentException expected."); + } catch (IllegalArgumentException ee) { + // expected. + } + infl1.end(); + } + + /** + * @tests java.util.zip.Inflater#setDictionary(byte[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Checks setDictionary. Can be splitted for 2 separate tests for Deflater & Inflater. Can be used as a base for setDictionary with additional params.", + method = "setDictionary", + args = {byte[].class, int.class, int.class} + ) + public void testsetDictionary$BII() throws Exception { + int i = 0; + String inputString = "blah string contains blahblahblahblah and blah"; + String dictionary1 = "blah"; + String dictionary2 = "blahblahblah"; + + byte[] output1 = new byte[100]; + byte[] output2 = new byte[100]; + byte[] output3 = new byte[100]; + + Deflater defDict1 = new Deflater(9); + Deflater defDict2 = new Deflater(9); + Deflater defDict3 = new Deflater(9); + + defDict1.setDictionary(dictionary1.getBytes()); + defDict2.setDictionary(dictionary2.getBytes()); + defDict3.setDictionary(dictionary2.getBytes(), 4, 4); + + defDict1.setInput(inputString.getBytes()); + defDict2.setInput(inputString.getBytes()); + defDict3.setInput(inputString.getBytes()); + + defDict1.finish(); + defDict2.finish(); + defDict3.finish(); + + int dataLen1 = defDict1.deflate(output1); + int dataLen2 = defDict2.deflate(output2); + int dataLen3 = defDict3.deflate(output3); + + boolean pass12 = false; + boolean pass23 = false; + boolean pass13 = true; + + for (i = 0; i < (dataLen1 < dataLen2 ? dataLen1 : dataLen2); i++) { + if (output1[i] != output2[i]) { + pass12 = true; + break; + } + } + for (i = 0; i < (dataLen2 < dataLen3 ? dataLen2 : dataLen3); i++) { + if (output2[i] != output3[i]) { + pass23 = true; + break; + } + } + for (i = 0; i < (dataLen1 < dataLen3 ? dataLen1 : dataLen3); i++) { + if (output1[i] != output3[i]) { + pass13 = false; + break; + } + } + + assertTrue( + "Compressed data the same for stream with different dictionaries.", + pass12); + assertTrue( + "Compressed data the same for stream with different dictionaries.", + pass23); + assertTrue( + "Compressed data the differs for stream with the same dictionaries.", + pass13); + + Inflater infl1 = new Inflater(); + Inflater infl2 = new Inflater(); + Inflater infl3 = new Inflater(); + + byte[] result = new byte[100]; + int decLen; + + infl1.setInput(output1, 0, dataLen1); + decLen = infl1.inflate(result); + + assertTrue(infl1.needsDictionary()); + infl1.setDictionary(dictionary2.getBytes(), 4, 4); + decLen = infl1.inflate(result); + infl1.end(); + assertEquals(inputString, new String(result, 0, decLen)); + + infl2.setInput(output2, 0, dataLen2); + decLen = infl2.inflate(result); + + assertTrue(infl2.needsDictionary()); + try { + infl2.setDictionary(dictionary1.getBytes()); + fail("IllegalArgumentException expected."); + } catch (IllegalArgumentException ee) { + // expected + } + infl2.end(); + + infl3.setInput(output3, 0, dataLen3); + decLen = infl3.inflate(result); + + assertTrue(infl3.needsDictionary()); + infl3.setDictionary(dictionary1.getBytes()); + decLen = infl3.inflate(result); + infl3.end(); + assertEquals(inputString, new String(result, 0, decLen)); + + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipEntryTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipEntryTest.java new file mode 100644 index 0000000..f8862d4 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipEntryTest.java @@ -0,0 +1,646 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import tests.support.resource.Support_Resources; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.TimeZone; +import java.util.zip.ZipEntry; + +@TestTargetClass(ZipEntry.class) +public class ZipEntryTest extends junit.framework.TestCase { + + // BEGIN android-added + public byte[] getAllBytesFromStream(InputStream is) throws IOException { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + byte[] buf = new byte[666]; + int iRead; + int off; + while (is.available() > 0) { + iRead = is.read(buf, 0, buf.length); + if (iRead > 0) bs.write(buf, 0, iRead); + } + return bs.toByteArray(); + } + + // END android-added + + // zip file hyts_ZipFile.zip must be included as a resource + java.util.zip.ZipEntry zentry; + + java.util.zip.ZipFile zfile; + + private static final String platformId = System.getProperty( + "com.ibm.oti.configuration", "JDK") + + System.getProperty("java.vm.version"); + + static final String tempFileName = platformId + "zfzezi.zip"; + + long orgSize; + + long orgCompressedSize; + + long orgCrc; + + long orgTime; + + String orgComment; + + /** + * @tests java.util.zip.ZipEntry#ZipEntry(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ZipEntry", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() { + // Test for method java.util.zip.ZipEntry(java.lang.String) + zentry = zfile.getEntry("File3.txt"); + assertNotNull("Failed to create ZipEntry", zentry); + try { + zentry = zfile.getEntry(null); + fail("NullPointerException not thrown"); + } catch (NullPointerException e) { + } + StringBuffer s = new StringBuffer(); + for (int i = 0; i < 65535; i++) { + s.append('a'); + } + try { + zentry = new ZipEntry(s.toString()); + } catch (IllegalArgumentException e) { + fail("Unexpected IllegalArgumentException During Test."); + } + try { + s.append('a'); + zentry = new ZipEntry(s.toString()); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + try { + String n = null; + zentry = new ZipEntry(n); + fail("NullPointerException not thrown"); + } catch (NullPointerException e) { + } + } + + /** + * @tests java.util.zip.ZipEntry#getComment() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getComment", + args = {} + ) + public void test_getComment() { + // Test for method java.lang.String java.util.zip.ZipEntry.getComment() + ZipEntry zipEntry = new ZipEntry("zippy.zip"); + assertNull("Incorrect Comment Returned.", zipEntry.getComment()); + zipEntry.setComment("This Is A Comment"); + assertEquals("Incorrect Comment Returned.", "This Is A Comment", + zipEntry.getComment()); + } + + /** + * @tests java.util.zip.ZipEntry#getCompressedSize() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCompressedSize", + args = {} + ) + public void test_getCompressedSize() { + // Test for method long java.util.zip.ZipEntry.getCompressedSize() + assertTrue("Incorrect compressed size returned", zentry + .getCompressedSize() == orgCompressedSize); + } + + /** + * @tests java.util.zip.ZipEntry#getCrc() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getCrc", + args = {} + ) + public void test_getCrc() { + // Test for method long java.util.zip.ZipEntry.getCrc() + assertTrue("Failed to get Crc", zentry.getCrc() == orgCrc); + } + + /** + * @tests java.util.zip.ZipEntry#getExtra() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getExtra", + args = {} + ) + public void test_getExtra() { + // Test for method byte [] java.util.zip.ZipEntry.getExtra() + assertNull("Incorrect extra information returned", zentry.getExtra()); + byte[] ba = {'T', 'E', 'S', 'T'}; + zentry = new ZipEntry("test.tst"); + zentry.setExtra(ba); + assertTrue("Incorrect Extra Information Returned.", + zentry.getExtra() == ba); + } + + /** + * @tests java.util.zip.ZipEntry#getMethod() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getMethod", + args = {} + ) + public void test_getMethod() { + // Test for method int java.util.zip.ZipEntry.getMethod() + zentry = zfile.getEntry("File1.txt"); + assertTrue("Incorrect compression method returned", + zentry.getMethod() == java.util.zip.ZipEntry.STORED); + zentry = zfile.getEntry("File3.txt"); + assertTrue("Incorrect compression method returned", + zentry.getMethod() == java.util.zip.ZipEntry.DEFLATED); + zentry = new ZipEntry("test.tst"); + assertEquals("Incorrect Method Returned.", -1, zentry.getMethod()); + } + + /** + * @tests java.util.zip.ZipEntry#getName() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getName", + args = {} + ) + public void test_getName() { + // Test for method java.lang.String java.util.zip.ZipEntry.getName() + assertEquals( + "Incorrect name returned - Note return result somewhat ambiguous in spec", + "File1.txt", zentry.getName()); + } + + /** + * @tests java.util.zip.ZipEntry#getSize() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getSize", + args = {} + ) + public void test_getSize() { + // Test for method long java.util.zip.ZipEntry.getSize() + assertTrue("Incorrect size returned", zentry.getSize() == orgSize); + } + + /** + * @tests java.util.zip.ZipEntry#getTime() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getTime", + args = {} + ) + public void test_getTime() { + // Test for method long java.util.zip.ZipEntry.getTime() + assertTrue("Failed to get time", zentry.getTime() == orgTime); + } + + /** + * @tests java.util.zip.ZipEntry#isDirectory() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "isDirectory", + args = {} + ) + public void test_isDirectory() { + // Test for method boolean java.util.zip.ZipEntry.isDirectory() + assertTrue("Entry should not answer true to isDirectory", !zentry + .isDirectory()); + zentry = new ZipEntry("Directory/"); + assertTrue("Entry should answer true to isDirectory", zentry + .isDirectory()); + } + + /** + * @tests java.util.zip.ZipEntry#setComment(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setComment", + args = {java.lang.String.class} + ) + public void test_setCommentLjava_lang_String() { + // Test for method void + // java.util.zip.ZipEntry.setComment(java.lang.String) + zentry = zfile.getEntry("File1.txt"); + zentry.setComment("Set comment using api"); + assertEquals("Comment not correctly set", "Set comment using api", + zentry.getComment()); + String n = null; + zentry.setComment(n); + assertNull("Comment not correctly set", zentry.getComment()); + StringBuffer s = new StringBuffer(); + for (int i = 0; i < 0xFFFF; i++) { + s.append('a'); + } + try { + zentry.setComment(s.toString()); + } catch (IllegalArgumentException e) { + fail("Unexpected IllegalArgumentException During Test."); + } + try { + s.append('a'); + zentry.setComment(s.toString()); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + /** + * @tests java.util.zip.ZipEntry#setCompressedSize(long) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setCompressedSize", + args = {long.class} + ) + public void test_setCompressedSizeJ() { + // Test for method void java.util.zip.ZipEntry.setCompressedSize(long) + zentry.setCompressedSize(orgCompressedSize + 10); + assertTrue("Set compressed size failed", + zentry.getCompressedSize() == (orgCompressedSize + 10)); + zentry.setCompressedSize(0); + assertEquals("Set compressed size failed", 0, zentry + .getCompressedSize()); + zentry.setCompressedSize(-25); + assertEquals("Set compressed size failed", -25, zentry + .getCompressedSize()); + zentry.setCompressedSize(4294967296l); + assertTrue("Set compressed size failed", + zentry.getCompressedSize() == 4294967296l); + } + + /** + * @tests java.util.zip.ZipEntry#setCrc(long) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setCrc", + args = {long.class} + ) + public void test_setCrcJ() { + // Test for method void java.util.zip.ZipEntry.setCrc(long) + zentry.setCrc(orgCrc + 100); + assertTrue("Failed to set Crc", zentry.getCrc() == (orgCrc + 100)); + zentry.setCrc(0); + assertEquals("Failed to set Crc", 0, zentry.getCrc()); + try { + zentry.setCrc(-25); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + try { + zentry.setCrc(4294967295l); + } catch (IllegalArgumentException e) { + fail("Unexpected IllegalArgumentException during test"); + } + try { + zentry.setCrc(4294967296l); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + /** + * @tests java.util.zip.ZipEntry#setExtra(byte[]) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setExtra", + args = {byte[].class} + ) + public void test_setExtra$B() { + // Test for method void java.util.zip.ZipEntry.setExtra(byte []) + zentry = zfile.getEntry("File1.txt"); + zentry.setExtra("Test setting extra information".getBytes()); + assertEquals("Extra information not written properly", + "Test setting extra information", new String(zentry.getExtra(), + 0, zentry.getExtra().length)); + zentry = new ZipEntry("test.tst"); + byte[] ba = new byte[0xFFFF]; + try { + zentry.setExtra(ba); + } catch (IllegalArgumentException e) { + fail("Unexpected IllegalArgumentException during test"); + } + try { + ba = new byte[0xFFFF + 1]; + zentry.setExtra(ba); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + + // One constructor + ZipEntry zeInput = new ZipEntry("InputZIP"); + byte[] extraB = {'a', 'b', 'd', 'e'}; + zeInput.setExtra(extraB); + assertEquals(extraB, zeInput.getExtra()); + assertEquals(extraB[3], zeInput.getExtra()[3]); + assertEquals(extraB.length, zeInput.getExtra().length); + + // test another constructor + ZipEntry zeOutput = new ZipEntry(zeInput); + assertEquals(zeInput.getExtra()[3], zeOutput.getExtra()[3]); + assertEquals(zeInput.getExtra().length, zeOutput.getExtra().length); + assertEquals(extraB[3], zeOutput.getExtra()[3]); + assertEquals(extraB.length, zeOutput.getExtra().length); + } + + /** + * @tests java.util.zip.ZipEntry#setMethod(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMethod", + args = {int.class} + ) + public void test_setMethodI() { + // Test for method void java.util.zip.ZipEntry.setMethod(int) + zentry = zfile.getEntry("File3.txt"); + zentry.setMethod(ZipEntry.STORED); + assertTrue("Failed to set compression method", + zentry.getMethod() == ZipEntry.STORED); + zentry.setMethod(ZipEntry.DEFLATED); + assertTrue("Failed to set compression method", + zentry.getMethod() == ZipEntry.DEFLATED); + try { + int error = 1; + zentry = new ZipEntry("test.tst"); + zentry.setMethod(error); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + /** + * @tests java.util.zip.ZipEntry#setSize(long) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setSize", + args = {long.class} + ) + public void test_setSizeJ() { + // Test for method void java.util.zip.ZipEntry.setSize(long) + zentry.setSize(orgSize + 10); + assertTrue("Set size failed", zentry.getSize() == (orgSize + 10)); + zentry.setSize(0); + assertEquals("Set size failed", 0, zentry.getSize()); + try { + zentry.setSize(-25); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + try { + zentry.setCrc(4294967295l); + } catch (IllegalArgumentException e) { + fail("Unexpected IllegalArgumentException during test"); + } + try { + zentry.setCrc(4294967296l); + fail("IllegalArgumentException not thrown"); + } catch (IllegalArgumentException e) { + } + } + + /** + * @tests java.util.zip.ZipEntry#setTime(long) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setTime", + args = {long.class} + ) + public void test_setTimeJ() { + // Test for method void java.util.zip.ZipEntry.setTime(long) + zentry.setTime(orgTime + 10000); + assertTrue("Test 1: Failed to set time: " + zentry.getTime(), zentry + .getTime() == (orgTime + 10000)); + zentry.setTime(orgTime - 10000); + assertTrue("Test 2: Failed to set time: " + zentry.getTime(), zentry + .getTime() == (orgTime - 10000)); + TimeZone zone = TimeZone.getDefault(); + try { + TimeZone.setDefault(TimeZone.getTimeZone("EST")); + zentry.setTime(0); + assertTrue("Test 3: Failed to set time: " + zentry.getTime(), + zentry.getTime() == 315550800000L); + TimeZone.setDefault(TimeZone.getTimeZone("GMT")); + assertTrue("Test 3a: Failed to set time: " + zentry.getTime(), + zentry.getTime() == 315532800000L); + zentry.setTime(0); + TimeZone.setDefault(TimeZone.getTimeZone("EST")); + assertTrue("Test 3b: Failed to set time: " + zentry.getTime(), + zentry.getTime() == 315550800000L); + + zentry.setTime(-25); + assertTrue("Test 4: Failed to set time: " + zentry.getTime(), + zentry.getTime() == 315550800000L); + zentry.setTime(4354837200000L); + assertTrue("Test 5: Failed to set time: " + zentry.getTime(), + zentry.getTime() == 315550800000L); + } finally { + TimeZone.setDefault(zone); + } + } + + /** + * @tests java.util.zip.ZipEntry#toString() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "toString", + args = {} + ) + public void test_toString() { + // Test for method java.lang.String java.util.zip.ZipEntry.toString() + assertTrue("Returned incorrect entry name", zentry.toString().indexOf( + "File1.txt") >= 0); + } + + /** + * @tests java.util.zip.ZipEntry#ZipEntry(java.util.zip.ZipEntry) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ZipEntry", + args = {java.util.zip.ZipEntry.class} + ) + public void test_ConstructorLjava_util_zip_ZipEntry() { + // Test for method java.util.zip.ZipEntry(util.zip.ZipEntry) + zentry.setSize(2); + zentry.setCompressedSize(4); + zentry.setComment("Testing"); + ZipEntry zentry2 = new ZipEntry(zentry); + assertEquals("ZipEntry Created With Incorrect Size.", 2, zentry2 + .getSize()); + assertEquals("ZipEntry Created With Incorrect Compressed Size.", 4, + zentry2.getCompressedSize()); + assertEquals("ZipEntry Created With Incorrect Comment.", "Testing", + zentry2.getComment()); + assertTrue("ZipEntry Created With Incorrect Crc.", + zentry2.getCrc() == orgCrc); + assertTrue("ZipEntry Created With Incorrect Time.", + zentry2.getTime() == orgTime); + } + + /** + * @tests java.util.zip.ZipEntry#clone() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "clone", + args = {} + ) + public void test_clone() { + // Test for method java.util.zip.ZipEntry.clone() + Object obj = zentry.clone(); + assertTrue("toString()", obj.toString().equals(zentry.toString())); + assertTrue("hashCode()", obj.hashCode() == zentry.hashCode()); + + // One constructor + ZipEntry zeInput = new ZipEntry("InputZIP"); + byte[] extraB = {'a', 'b', 'd', 'e'}; + zeInput.setExtra(extraB); + assertEquals(extraB, zeInput.getExtra()); + assertEquals(extraB[3], zeInput.getExtra()[3]); + assertEquals(extraB.length, zeInput.getExtra().length); + + // test Clone() + ZipEntry zeOutput = (ZipEntry) zeInput.clone(); + assertEquals(zeInput.getExtra()[3], zeOutput.getExtra()[3]); + assertEquals(zeInput.getExtra().length, zeOutput.getExtra().length); + assertEquals(extraB[3], zeOutput.getExtra()[3]); + assertEquals(extraB.length, zeOutput.getExtra().length); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "hashCode", + args = {} + ) + public void test_hashCode() { + try { + zentry.hashCode(); + } catch (Exception ee) { + fail("Unexpected exception " + ee); + } + } + + /** + * Sets up the fixture, for example, open a network connection. This method + * is called before a test is executed. + */ + + @Override + protected void setUp() { + java.io.File f = null; + try { + // BEGIN android-changed + // Create a local copy of the file since some tests want to alter + // information. + f = new java.io.File(tempFileName); + // Create absolute filename as ZipFile does not resolve using + // user.dir + f = new java.io.File(f.getAbsolutePath()); + f.delete(); + java.io.InputStream is = Support_Resources + .getStream("hyts_ZipFile.zip"); + java.io.FileOutputStream fos = new java.io.FileOutputStream(f); + byte[] rbuf = getAllBytesFromStream(is); + // END android-changed + fos.write(rbuf, 0, rbuf.length); + is.close(); + fos.close(); + zfile = new java.util.zip.ZipFile(f); + zentry = zfile.getEntry("File1.txt"); + orgSize = zentry.getSize(); + orgCompressedSize = zentry.getCompressedSize(); + orgCrc = zentry.getCrc(); + orgTime = zentry.getTime(); + orgComment = zentry.getComment(); + } catch (Exception e) { + System.out.println("Exception during ZipFile setup <" + + f.getAbsolutePath() + ">: "); + e.printStackTrace(); + } + } + + /** + * Tears down the fixture, for example, close a network connection. This + * method is called after a test is executed. + */ + + @Override + protected void tearDown() { + try { + if (zfile != null) { + zfile.close(); + } + java.io.File f = new java.io.File(tempFileName); + f.delete(); + } catch (java.io.IOException e) { + System.out.println("Exception during tearDown"); + } + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipExceptionTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipExceptionTest.java new file mode 100644 index 0000000..eb04777 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipExceptionTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; +import dalvik.annotation.TestTargetClass; + +import junit.framework.TestCase; + +import java.util.zip.ZipException; + +@TestTargetClass(ZipException.class) +public class ZipExceptionTest extends TestCase { + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ZipException", + args = {} + ) + public void testZipException() { + ZipException zz = new ZipException(); + assertEquals(zz.getMessage(), null); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ZipException", + args = {java.lang.String.class} + ) + public void testZipExceptionLjava_lang_String() { + ZipException zz = new ZipException("Test"); + assertEquals(zz.getMessage(), "Test"); + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipFileTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipFileTest.java new file mode 100644 index 0000000..3fe23a7 --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipFileTest.java @@ -0,0 +1,515 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.AndroidOnly; +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; +import tests.support.Support_PlatformFile; +import tests.support.resource.Support_Resources; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.security.Permission; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +@TestTargetClass(ZipFile.class) +public class ZipFileTest extends junit.framework.TestCase { + + // BEGIN android-added + public byte[] getAllBytesFromStream(InputStream is) throws IOException { + ByteArrayOutputStream bs = new ByteArrayOutputStream(); + byte[] buf = new byte[666]; + int iRead; + int off; + while (is.available() > 0) { + iRead = is.read(buf, 0, buf.length); + if (iRead > 0) bs.write(buf, 0, iRead); + } + return bs.toByteArray(); + } + + // END android-added + + // the file hyts_zipFile.zip in setup must be included as a resource + private String tempFileName; + + private ZipFile zfile; + + // custom security manager + SecurityManager sm = new SecurityManager() { + final String forbidenPermissionAction = "read"; + + + + public void checkPermission(Permission perm) { + if (perm.getActions().equals(forbidenPermissionAction)) { + throw new SecurityException(); + } + } + }; + + /** + * @tests java.util.zip.ZipFile#ZipFile(java.io.File) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "setUp procedure checks this type of constructor.", + method = "ZipFile", + args = {java.io.File.class} + ) + public void test_ConstructorLjava_io_File() { + // Test for method java.util.zip.ZipFile(java.io.File) + assertTrue("Used to test", true); + } + + /** + * @tests java.util.zip.ZipFile#ZipFile(java.io.File, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "ZipFile", + args = {java.io.File.class, int.class} + ) + public void test_ConstructorLjava_io_FileI() throws IOException { + zfile.close(); // about to reopen the same temp file + File file = new File(tempFileName); + ZipFile zip = new ZipFile(file, ZipFile.OPEN_DELETE | ZipFile.OPEN_READ); + zip.close(); + assertTrue("Zip should not exist", !file.exists()); + file = new File(tempFileName); + file.delete(); + try { + zip = new ZipFile(file, ZipFile.OPEN_READ); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + // IOException can not be checked throws ZipException in case of IO + // problems. + SecurityManager oldSm = System.getSecurityManager(); + System.setSecurityManager(sm); + try { + file = new File(tempFileName); + zip = new ZipFile(file, ZipFile.OPEN_READ); + fail("SecurityException expected"); + } catch (SecurityException e) { + // expected + } finally { + System.setSecurityManager(oldSm); + } + file = new File(tempFileName); + try { + zip = new ZipFile(file, -1); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException ee) { + // expected + } + } + + /** + * @throws IOException + * @tests java.util.zip.ZipFile#ZipFile(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Test contains empty brackets.", + method = "ZipFile", + args = {java.lang.String.class} + ) + public void test_ConstructorLjava_lang_String() throws IOException { + String oldUserDir = System.getProperty("user.dir"); + System.setProperty("user.dir", System.getProperty("java.io.tmpdir")); + + zfile.close(); // about to reopen the same temp file + ZipFile zip = new ZipFile(tempFileName); + zip.close(); + File file = File.createTempFile("zip", "tmp"); + try { + zip = new ZipFile(file.getName()); + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + file.delete(); + // IOException can not be checked throws ZipException in case of IO + // problems. + SecurityManager oldSm = System.getSecurityManager(); + System.setSecurityManager(sm); + try { + zip = new ZipFile(tempFileName); + fail("SecurityException expected"); + } catch (SecurityException e) { + // expected + } finally { + System.setSecurityManager(oldSm); + System.setProperty("user.dir", oldUserDir); + } + } + + protected ZipEntry test_finalize1(ZipFile zip) { + return zip.getEntry("File1.txt"); + } + + protected ZipFile test_finalize2(File file) throws IOException { + return new ZipFile(file); + } + + /** + * @tests java.util.zip.ZipFile#finalize() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "finalize", + args = {} + ) + public void test_finalize() throws IOException { + InputStream in = Support_Resources.getStream("hyts_ZipFile.zip"); + File file = Support_Resources.createTempFile(".jar"); + OutputStream out = new FileOutputStream(file); + int result; + byte[] buf = new byte[4096]; + while ((result = in.read(buf)) != -1) { + out.write(buf, 0, result); + } + in.close(); + out.close(); + /* + * ZipFile zip = new ZipFile(file); ZipEntry entry1 = + * zip.getEntry("File1.txt"); assertNotNull("Did not find entry", + * entry1); entry1 = null; zip = null; + */ + + assertNotNull("Did not find entry", + test_finalize1(test_finalize2(file))); + System.gc(); + System.gc(); + System.runFinalization(); + file.delete(); + assertTrue("Zip should not exist", !file.exists()); + } + + /** + * @throws IOException + * @tests java.util.zip.ZipFile#close() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "close", + args = {} + ) + public void test_close() throws IOException { + // Test for method void java.util.zip.ZipFile.close() + File fl = new File(tempFileName); + ZipFile zf = new ZipFile(fl); + InputStream is1 = zf.getInputStream(zf.getEntry("File1.txt")); + InputStream is2 = zf.getInputStream(zf.getEntry("File2.txt")); + + is1.read(); + is2.read(); + + zf.close(); + + try { + is1.read(); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + + try { + is2.read(); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + /** + * @tests java.util.zip.ZipFile#entries() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "entries", + args = {} + ) + public void test_entries() { + // Test for method java.util.Enumeration java.util.zip.ZipFile.entries() + Enumeration<? extends ZipEntry> enumer = zfile.entries(); + int c = 0; + while (enumer.hasMoreElements()) { + ++c; + enumer.nextElement(); + } + assertTrue("Incorrect number of entries returned: " + c, c == 6); + + try { + Enumeration<? extends ZipEntry> enumeration = zfile.entries(); + zfile.close(); + zfile = null; + boolean pass = false; + try { + enumeration.hasMoreElements(); + } catch (IllegalStateException e) { + pass = true; + } + assertTrue("did not detect closed jar file", pass); + } catch (Exception e) { + fail("Exception during entries test: " + e.toString()); + } + } + + /** + * @tests java.util.zip.ZipFile#getEntry(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + method = "getEntry", + args = {java.lang.String.class} + ) + public void test_getEntryLjava_lang_String() throws IOException { + // Test for method java.util.zip.ZipEntry + // java.util.zip.ZipFile.getEntry(java.lang.String) + java.util.zip.ZipEntry zentry = zfile.getEntry("File1.txt"); + assertNotNull("Could not obtain ZipEntry", zentry); + int r; + InputStream in; + + zentry = zfile.getEntry("testdir1/File1.txt"); + assertNotNull("Could not obtain ZipEntry: testdir1/File1.txt", zentry); + zentry = zfile.getEntry("testdir1/"); + assertNotNull("Could not obtain ZipEntry: testdir1/", zentry); + in = zfile.getInputStream(zentry); + assertNotNull("testdir1/ should not have null input stream", in); + r = in.read(); + in.close(); + assertEquals("testdir1/ should not contain data", -1, r); + + zentry = zfile.getEntry("testdir1/testdir1"); + assertNotNull("Could not obtain ZipEntry: testdir1/testdir1", zentry); + in = zfile.getInputStream(zentry); + byte[] buf = new byte[256]; + r = in.read(buf); + in.close(); + assertEquals("incorrect contents", "This is also text", new String(buf, + 0, r)); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Strange test that succeeds in Android but not against RI.", + method = "getEntry", + args = {java.lang.String.class} + ) + @AndroidOnly("God knows why!") + public void test_getEntryLjava_lang_String_AndroidOnly() throws IOException { + java.util.zip.ZipEntry zentry = zfile.getEntry("File1.txt"); + assertNotNull("Could not obtain ZipEntry", zentry); + int r; + InputStream in; + + zentry = zfile.getEntry("testdir1"); + assertNotNull("Could not obtain ZipEntry: testdir1", zentry); + in = zfile.getInputStream(zentry); + assertNotNull("testdir1 should not have null input stream", in); + r = in.read(); + in.close(); + assertEquals("testdir1 should not contain data", -1, r); + } + + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "IllegalStateException checking.", + method = "getEntry", + args = {java.lang.String.class} + ) + @KnownFailure("Android does not throw IllegalStateException when using getEntry() after close().") + public void test_getEntryLjava_lang_String_Ex() throws IOException { + java.util.zip.ZipEntry zentry = zfile.getEntry("File1.txt"); + assertNotNull("Could not obtain ZipEntry", zentry); + int r; + InputStream in; + + zfile.close(); + try { + zentry = zfile.getEntry("File2.txt"); + fail("IllegalStateException expected"); // Android fails here! + } catch (IllegalStateException ee) { + // expected + } + } + + /** + * @throws IOException + * @tests java.util.zip.ZipFile#getInputStream(java.util.zip.ZipEntry) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getInputStream", + args = {java.util.zip.ZipEntry.class} + ) + public void test_getInputStreamLjava_util_zip_ZipEntry() throws IOException { + // Test for method java.io.InputStream + // java.util.zip.ZipFile.getInputStream(java.util.zip.ZipEntry) + ZipEntry zentry = null; + InputStream is = null; + try { + zentry = zfile.getEntry("File1.txt"); + is = zfile.getInputStream(zentry); + byte[] rbuf = new byte[1000]; + int r; + is.read(rbuf, 0, r = (int) zentry.getSize()); + assertEquals("getInputStream read incorrect data", "This is text", + new String(rbuf, 0, r)); + } catch (java.io.IOException e) { + fail("IOException during getInputStream"); + } finally { + try { + is.close(); + } catch (java.io.IOException e) { + fail("Failed to close input stream"); + } + } + + zentry = zfile.getEntry("File2.txt"); + zfile.close(); + try { + is = zfile.getInputStream(zentry); + fail("IllegalStateException expected"); + } catch (IllegalStateException ee) { + // expected + } + + // ZipException can not be checked. Stream object returned or null. + } + + /** + * @tests java.util.zip.ZipFile#getName() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "getName", + args = {} + ) + public void test_getName() { + // Test for method java.lang.String java.util.zip.ZipFile.getName() + assertTrue("Returned incorrect name: " + zfile.getName(), zfile + .getName().equals(tempFileName)); + } + + /** + * @throws IOException + * @tests java.util.zip.ZipFile#size() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "size", + args = {} + ) + @KnownFailure("IllegalStateException not thrown when using ZipFile.size() after close().") + public void test_size() throws IOException { + assertEquals(6, zfile.size()); + zfile.close(); + try { + zfile.size(); + fail("IllegalStateException expected"); // Android fails here! + } catch (IllegalStateException ee) { + // expected + } + } + + /** + * Sets up the fixture, for example, open a network connection. This method + * is called before a test is executed. + */ + @Override + protected void setUp() { + try { + // BEGIN android-changed + // Create a local copy of the file since some tests want to alter + // information. + tempFileName = System.getProperty("java.io.tmpdir"); + String separator = System.getProperty("file.separator"); + if (tempFileName.charAt(tempFileName.length() - 1) == separator + .charAt(0)) { + tempFileName = Support_PlatformFile.getNewPlatformFile( + tempFileName, "gabba.zip"); + } else { + tempFileName = Support_PlatformFile.getNewPlatformFile( + tempFileName + separator, "gabba.zip"); + } + + File f = new File(tempFileName); + f.delete(); + InputStream is = Support_Resources.getStream("hyts_ZipFile.zip"); + FileOutputStream fos = new FileOutputStream(f); + // END android-changed + byte[] rbuf = getAllBytesFromStream(is); + fos.write(rbuf, 0, rbuf.length); + is.close(); + fos.close(); + zfile = new ZipFile(f); + } catch (Exception e) { + System.out.println("Exception during ZipFile setup:"); + e.printStackTrace(); + } + } + + /** + * Tears down the fixture, for example, close a network connection. This + * method is called after a test is executed. + */ + @Override + protected void tearDown() { + try { + if (zfile != null) { + // Note zfile is a user-defined zip file used by other tests and + // should not be deleted + zfile.close(); + tempFileName = System.getProperty("java.io.tmpdir"); + String separator = System.getProperty("file.separator"); + if (tempFileName.charAt(tempFileName.length() - 1) == separator + .charAt(0)) { + tempFileName = Support_PlatformFile.getNewPlatformFile( + tempFileName, "gabba.zip"); + } else { + tempFileName = Support_PlatformFile.getNewPlatformFile( + tempFileName + separator, "gabba.zip"); + } + + File f = new File(tempFileName); + f.delete(); + } + } catch (Exception e) { + } + } + +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipInputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipInputStreamTest.java new file mode 100644 index 0000000..bdab74a --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipInputStreamTest.java @@ -0,0 +1,419 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.KnownFailure; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargetNew; + +import junit.framework.TestCase; + +import tests.support.resource.Support_Resources; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +@TestTargetClass(ZipInputStream.class) +public class ZipInputStreamTest extends TestCase { + // the file hyts_zipFile.zip used in setup needs to included as a resource + private ZipEntry zentry; + + private ZipInputStream zis; + + private byte[] zipBytes; + + private byte[] dataBytes = "Some data in my file".getBytes(); + + @Override + protected void setUp() { + try { + InputStream is = Support_Resources.getStream("hyts_ZipFile.zip"); + if (is == null) { + System.out.println("file hyts_ZipFile.zip can not be found"); + } + zis = new ZipInputStream(is); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ZipOutputStream zos = new ZipOutputStream(bos); + ZipEntry entry = new ZipEntry("myFile"); + zos.putNextEntry(entry); + zos.write(dataBytes); + zos.closeEntry(); + zos.close(); + zipBytes = bos.toByteArray(); + } catch (Exception e) { + System.out.println("Exception during ZipFile setup:"); + e.printStackTrace(); + } + } + + @Override + protected void tearDown() { + if (zis != null) { + try { + zis.close(); + } catch (Exception e) { + } + } + } + + /** + * @tests java.util.zip.ZipInputStream#ZipInputStream(java.io.InputStream) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "ZipInputStream", + args = {java.io.InputStream.class} + ) + public void test_ConstructorLjava_io_InputStream() throws Exception { + zentry = zis.getNextEntry(); + zis.closeEntry(); + } + + /** + * @tests java.util.zip.ZipInputStream#close() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "close", + args = {} + ) + public void test_close() { + try { + zis.close(); + byte[] rbuf = new byte[10]; + zis.read(rbuf, 0, 1); + } catch (IOException e) { + return; + } + fail("Read data after stream was closed"); + } + + /** + * @tests java.util.zip.ZipInputStream#close() + */ + @TestTargetNew( + level = TestLevel.PARTIAL_COMPLETE, + notes = "Checks calling method two times", + method = "close", + args = {} + ) + public void test_close2() throws Exception { + // Regression for HARMONY-1101 + zis.close(); + // another call to close should NOT cause an exception + zis.close(); + } + + /** + * @tests java.util.zip.ZipInputStream#closeEntry() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "closeEntry", + args = {} + ) + public void test_closeEntry() throws Exception { + zentry = zis.getNextEntry(); + zis.closeEntry(); + zentry = zis.getNextEntry(); + zis.close(); + try { + zis.closeEntry(); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + FileInputStream fis = new FileInputStream(new File(resources, + "Broken_manifest.jar")); + + ZipInputStream zis1 = new ZipInputStream(fis); + + try { + for (int i = 0; i < 6; i++) { + zis1.getNextEntry(); + zis1.closeEntry(); + } + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "close", + args = {} + ) + @KnownFailure("The behaviour is different from RI, but not neccessarily wrong.") + public void test_closeAfterException() throws Exception { + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + FileInputStream fis = new FileInputStream(new File(resources, + "Broken_manifest.jar")); + + ZipInputStream zis1 = new ZipInputStream(fis); + + try { + for (int i = 0; i < 6; i++) { + zis1.getNextEntry(); + } + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + + zis1.close(); // Android throws exception here, but RI only when getNextEntry/read/skip are called. + try { + zis1.getNextEntry(); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + /** + * @tests java.util.zip.ZipInputStream#getNextEntry() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "getNextEntry", + args = {} + ) + public void test_getNextEntry() throws Exception { + assertNotNull("getNextEntry failed", zis.getNextEntry()); + + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + FileInputStream fis = new FileInputStream(new File(resources, + "Broken_manifest.jar")); + + ZipInputStream zis1 = new ZipInputStream(fis); + + try { + for (int i = 0; i < 6; i++) { + zis1.getNextEntry(); + } + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + + try { + zis1.close(); // Android throws exception here, already! + zis1.getNextEntry(); // But RI here, only! + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + /** + * @tests java.util.zip.ZipInputStream#read(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "read", + args = {byte[].class, int.class, int.class} + ) + public void test_read$BII() throws Exception { + zentry = zis.getNextEntry(); + byte[] rbuf = new byte[(int) zentry.getSize()]; + int r = zis.read(rbuf, 0, rbuf.length); + new String(rbuf, 0, r); + assertEquals("Failed to read entry", 12, r); + + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + FileInputStream fis = new FileInputStream(new File(resources, + "Broken_manifest.jar")); + + ZipInputStream zis1 = new ZipInputStream(fis); + + zis1.getNextEntry(); + zis1.getNextEntry(); + + rbuf = new byte[100]; + + try { + zis1.read(rbuf, 10, 90); + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + + try { + zis1.close(); // Android throws exception here, already! + zis1.read(rbuf, 10, 90); // But RI here, only! + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + /** + * @tests java.util.zip.ZipInputStream#skip(long) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "skip", + args = {long.class} + ) + public void test_skipJ() throws Exception { + zentry = zis.getNextEntry(); + byte[] rbuf = new byte[(int) zentry.getSize()]; + zis.skip(2); + int r = zis.read(rbuf, 0, rbuf.length); + assertEquals("Failed to skip data", 10, r); + + zentry = zis.getNextEntry(); + zentry = zis.getNextEntry(); + long s = zis.skip(1025); + assertTrue("invalid skip: " + s, s == 1025); + + ZipInputStream zis = new ZipInputStream(new ByteArrayInputStream( + zipBytes)); + zis.getNextEntry(); + long skipLen = dataBytes.length / 2; + assertEquals("Assert 0: failed valid skip", skipLen, zis.skip(skipLen)); + zis.skip(dataBytes.length); + assertEquals("Assert 1: performed invalid skip", 0, zis.skip(1)); + assertEquals("Assert 2: failed zero len skip", 0, zis.skip(0)); + try { + zis.skip(-1); + fail("Assert 3: Expected Illegal argument exception"); + } catch (IllegalArgumentException e) { + // Expected + } + + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + FileInputStream fis = new FileInputStream(new File(resources, + "Broken_manifest.jar")); + + ZipInputStream zis1 = new ZipInputStream(fis); + + zis1.getNextEntry(); + zis1.getNextEntry(); + + try { + zis1.skip(10); + fail("ZipException expected"); + } catch (ZipException ee) { + // expected + } + + try { + zis1.close(); // Android throws exception here, already! + zis1.skip(10); // But RI here, only! + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + method = "available", + args = {} + ) + @KnownFailure("Needs investigation!!!") + public void test_available() throws Exception { + + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + File fl = new File(resources, "Broken_manifest.jar"); + FileInputStream fis = new FileInputStream(fl); + + ZipInputStream zis1 = new ZipInputStream(fis); + int i = 0; +System.out.println(fl.length()); + for (i = 0; i < fl.length(); i++) { +//System.out.println(i); + zis1.skip(1); +//System.out.println("Skipped 1"); +int avail = zis1.available(); +//System.out.println(avail); + if (zis1.available() == 0) break; // RI breaks at i = 0 already; Android loops till the end! +//System.out.println("Looping..."); + } + if (i == fl.length()) { + fail("ZipInputStream.available or ZipInputStream.skip does not working properly"); + } + assertTrue(zis1.available() == 0); + zis1.skip(1); + assertFalse(zis.available() == 0); + zis1.close(); + try { + zis1.available(); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + class Mock_ZipInputStream extends ZipInputStream { + boolean createFlag = false; + + public Mock_ZipInputStream(InputStream arg0) { + super(arg0); + } + + boolean getCreateFlag() { + return createFlag; + } + + protected ZipEntry createZipEntry(String name) { + createFlag = true; + return super.createZipEntry(name); + } + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "createZipEntry", + args = {java.lang.String.class} + ) + public void test_createZipEntryLjava_lang_String() throws Exception { + + File resources = Support_Resources.createTempFolder(); + Support_Resources.copyFile(resources, null, "Broken_manifest.jar"); + File fl = new File(resources, "Broken_manifest.jar"); + FileInputStream fis = new FileInputStream(fl); + + Mock_ZipInputStream zis1 = new Mock_ZipInputStream(fis); + assertFalse(zis1.getCreateFlag()); + zis1.getNextEntry(); + assertTrue(zis1.getCreateFlag()); + } +} diff --git a/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipOutputStreamTest.java b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipOutputStreamTest.java new file mode 100644 index 0000000..9a5f63a --- /dev/null +++ b/archive/src/test/java/org/apache/harmony/archive/tests/java/util/zip/ZipOutputStreamTest.java @@ -0,0 +1,371 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.harmony.archive.tests.java.util.zip; + +import dalvik.annotation.TestTargetClass; +import dalvik.annotation.TestTargets; +import dalvik.annotation.TestLevel; +import dalvik.annotation.TestTargetNew; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.zip.CRC32; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipInputStream; +import java.util.zip.ZipOutputStream; + +@TestTargetClass(ZipOutputStream.class) +public class ZipOutputStreamTest extends junit.framework.TestCase { + + ZipOutputStream zos; + + ByteArrayOutputStream bos; + + ZipInputStream zis; + + static final String data = "HelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorldHelloWorld"; + + /** + * @tests java.util.zip.ZipOutputStream#close() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Can not check IOException.", + method = "close", + args = {} + ) + public void test_close() throws Exception { + try { + zos.close(); + fail("Close on empty stream failed to throw exception"); + } catch (ZipException e) { + // expected + } + + zos = new ZipOutputStream(bos); + zos.putNextEntry(new ZipEntry("XX")); + zos.closeEntry(); + zos.close(); + + // Regression for HARMONY-97 + ZipOutputStream zos = new ZipOutputStream(new ByteArrayOutputStream()); + zos.putNextEntry(new ZipEntry("myFile")); + zos.close(); + zos.close(); // Should be a no-op + } + + /** + * @tests java.util.zip.ZipOutputStream#closeEntry() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "ZipException can not be checked.", + method = "closeEntry", + args = {} + ) + public void test_closeEntry() throws IOException { + ZipEntry ze = new ZipEntry("testEntry"); + ze.setTime(System.currentTimeMillis()); + zos.putNextEntry(ze); + zos.write("Hello World".getBytes()); + zos.closeEntry(); + assertTrue("closeEntry failed to update required fields", + ze.getSize() == 11 && ze.getCompressedSize() == 13); + ze = new ZipEntry("testEntry1"); + zos.close(); + try { + zos.closeEntry(); + fail("IOException expected"); + } catch (IOException ee) { + // expected + } + } + + /** + * @tests java.util.zip.ZipOutputStream#finish() + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "ZipException can not be checked.", + method = "finish", + args = {} + ) + public void test_finish() throws Exception { + ZipEntry ze = new ZipEntry("test"); + zos.putNextEntry(ze); + zos.write("Hello World".getBytes()); + zos.finish(); + assertEquals("Finish failed to closeCurrentEntry", 11, ze.getSize()); + + ZipOutputStream zos = new ZipOutputStream(new ByteArrayOutputStream()); + zos.putNextEntry(new ZipEntry("myFile")); + zos.finish(); + zos.close(); + try { + zos.finish(); + fail("Assert 0: Expected IOException"); + } catch (IOException e) { + // Expected + } + } + + /** + * @tests java.util.zip.ZipOutputStream#putNextEntry(java.util.zip.ZipEntry) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "ZipException can not be checked.", + method = "putNextEntry", + args = {java.util.zip.ZipEntry.class} + ) + public void test_putNextEntryLjava_util_zip_ZipEntry() throws IOException { + ZipEntry ze = new ZipEntry("testEntry"); + ze.setTime(System.currentTimeMillis()); + zos.putNextEntry(ze); + zos.write("Hello World".getBytes()); + zos.closeEntry(); + zos.close(); + zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray())); + ZipEntry ze2 = zis.getNextEntry(); + zis.closeEntry(); + assertTrue("Failed to write correct entry", ze.getName().equals( + ze2.getName()) + && ze.getCrc() == ze2.getCrc()); + try { + zos.putNextEntry(ze); + fail("Entry with incorrect setting failed to throw exception"); + } catch (IOException e) { + // expected + } + } + + /** + * @tests java.util.zip.ZipOutputStream#setComment(java.lang.String) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setComment", + args = {java.lang.String.class} + ) + public void test_setCommentLjava_lang_String() { + // There is no way to get the comment back, so no way to determine if + // the comment is set correct + try { + zos.setComment("test setComment"); + } catch (Exception e) { + fail("Trying to set comment failed"); + } + try { + zos.setComment(new String(new byte[0xFFFF + 1])); + fail("Comment over 0xFFFF in length should throw exception"); + } catch (IllegalArgumentException e) { + // Passed + } + } + + /** + * @tests java.util.zip.ZipOutputStream#setLevel(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setLevel", + args = {int.class} + ) + public void test_setLevelI() throws IOException { + ZipEntry ze = new ZipEntry("test"); + zos.putNextEntry(ze); + zos.write(data.getBytes()); + zos.closeEntry(); + long csize = ze.getCompressedSize(); + zos.setLevel(9); // Max Compression + zos.putNextEntry(ze = new ZipEntry("test2")); + zos.write(data.getBytes()); + zos.closeEntry(); + assertTrue("setLevel failed", csize <= ze.getCompressedSize()); + try { + zos.setLevel(-9); // Max Compression + fail("IllegalArgumentException ecpected."); + } catch (IllegalArgumentException ee) { + // expected + } + } + + /** + * @tests java.util.zip.ZipOutputStream#setMethod(int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "setMethod", + args = {int.class} + ) + public void test_setMethodI() throws IOException { + ZipEntry ze = new ZipEntry("test"); + zos.setMethod(ZipOutputStream.STORED); + CRC32 tempCrc = new CRC32(); + tempCrc.update(data.getBytes()); + ze.setCrc(tempCrc.getValue()); + ze.setSize(new String(data).length()); + zos.putNextEntry(ze); + zos.write(data.getBytes()); + zos.closeEntry(); + long csize = ze.getCompressedSize(); + zos.setMethod(ZipOutputStream.DEFLATED); + zos.putNextEntry(ze = new ZipEntry("test2")); + zos.write(data.getBytes()); + zos.closeEntry(); + assertTrue("setLevel failed", csize >= ze.getCompressedSize()); + try { + zos.setMethod(-ZipOutputStream.DEFLATED); + fail("IllegalArgumentException expected"); + } catch (IllegalArgumentException ee) { + // expected + } + } + + /** + * @tests java.util.zip.ZipOutputStream#write(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "", + method = "write", + args = {byte[].class, int.class, int.class} + ) + public void test_write$BII() throws IOException { + ZipEntry ze = new ZipEntry("test"); + zos.putNextEntry(ze); + zos.write(data.getBytes()); + zos.closeEntry(); + zos.close(); + zos = null; + zis = new ZipInputStream(new ByteArrayInputStream(bos.toByteArray())); + zis.getNextEntry(); + byte[] b = new byte[data.length()]; + int r = 0; + int count = 0; + while (count != b.length && (r = zis.read(b, count, b.length)) != -1) { + count += r; + } + zis.closeEntry(); + assertTrue("Write failed to write correct bytes", new String(b) + .equals(data)); + + File f = File.createTempFile("testZip", "tst"); + f.deleteOnExit(); + FileOutputStream stream = new FileOutputStream(f); + ZipOutputStream zip = new ZipOutputStream(stream); + zip.setMethod(ZipEntry.STORED); + + try { + zip.putNextEntry(new ZipEntry("Second")); + fail("Not set an entry. Should have thrown ZipException."); + } catch (ZipException e) { + // expected -- We have not set an entry + } + + try { + // We try to write data without entry + zip.write(new byte[2]); + fail("Writing data without an entry. Should have thrown IOException"); + } catch (IOException e) { + // expected + } + + try { + // Try to write without an entry and with nonsense offset and + // length + zip.write(new byte[2], 0, 12); + fail("Writing data without an entry. Should have thrown IndexOutOfBoundsException"); + } catch (IndexOutOfBoundsException e) { + // expected + } + } + + /** + * @tests java.util.zip.ZipOutputStream#write(byte[], int, int) + */ + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "Regression", + method = "write", + args = {byte[].class, int.class, int.class} + ) + public void test_write$BII_2() throws IOException { + // Regression for HARMONY-577 + File f1 = File.createTempFile("testZip1", "tst"); + f1.deleteOnExit(); + FileOutputStream stream1 = new FileOutputStream(f1); + ZipOutputStream zip1 = new ZipOutputStream(stream1); + zip1.putNextEntry(new ZipEntry("one")); + zip1.setMethod(ZipOutputStream.STORED); + zip1.setMethod(ZipEntry.STORED); + + zip1.write(new byte[2]); + + try { + zip1.putNextEntry(new ZipEntry("Second")); + fail("ZipException expected"); + } catch (ZipException e) { + // expected - We have not set an entry + } + + try { + zip1.write(new byte[2]); // try to write data without entry + fail("expected IOE there"); + } catch (IOException e2) { + // expected + } + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + zos = new ZipOutputStream(bos = new ByteArrayOutputStream()); + } + + @Override + protected void tearDown() throws Exception { + try { + if (zos != null) { + zos.close(); + } + if (zis != null) { + zis.close(); + } + } catch (Exception e) { + } + super.tearDown(); + } + + @TestTargetNew( + level = TestLevel.COMPLETE, + notes = "See setUp procedure for more info.", + method = "ZipOutputStream", + args = {java.io.OutputStream.class} + ) + public void test_ConstructorLjava_io_OutputStream() { + assertTrue(true); + } +} diff --git a/archive/src/test/java/tests/archive/AllTests.java b/archive/src/test/java/tests/archive/AllTests.java new file mode 100644 index 0000000..a5c461c --- /dev/null +++ b/archive/src/test/java/tests/archive/AllTests.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tests.archive; + +import junit.framework.Test; +import junit.framework.TestSuite; + +public class AllTests { + + public static void main(String[] args) { + junit.textui.TestRunner.run(AllTests.suite()); + } + + public static Test suite() { + TestSuite suite = tests.TestSuiteFactory.createTestSuite("All Archive test suites"); + // $JUnit-BEGIN$ + suite.addTest(org.apache.harmony.archive.tests.java.util.jar.AllTests + .suite()); + suite.addTest(org.apache.harmony.archive.tests.java.util.zip.AllTests + .suite()); + // $JUnit-END$ + return suite; + } +} |