1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 #define LOG_TAG "Inflater" 19 20 #include "JniConstants.h" 21 #include "ScopedPrimitiveArray.h" 22 #include "zip.h" 23 #include <errno.h> 24 25 static struct { 26 jfieldID inRead; 27 jfieldID finished; 28 jfieldID needsDictionary; 29 } gCachedFields; 30 31 /* Create a new stream . This stream cannot be used until it has been properly initialized. */ 32 static jlong Inflater_createStream(JNIEnv* env, jobject, jboolean noHeader) { 33 UniquePtr<NativeZipStream> jstream(new NativeZipStream); 34 if (jstream.get() == NULL) { 35 jniThrowOutOfMemoryError(env, NULL); 36 return -1; 37 } 38 jstream->stream.adler = 1; 39 40 /* 41 * In the range 8..15 for checked, or -8..-15 for unchecked inflate. Unchecked 42 * is appropriate for formats like zip that do their own validity checking. 43 */ 44 /* Window bits to use. 15 is fastest but consumes the most memory */ 45 int wbits = 15; /*Use MAX for fastest */ 46 if (noHeader) { 47 wbits = wbits / -1; 48 } 49 int err = inflateInit2(&jstream->stream, wbits); 50 if (err != Z_OK) { 51 throwExceptionForZlibError(env, "java/lang/IllegalArgumentException", err); 52 return -1; 53 } 54 return reinterpret_cast<uintptr_t>(jstream.release()); 55 } 56 57 static void Inflater_setInputImpl(JNIEnv* env, jobject, jbyteArray buf, jint off, jint len, jlong handle) { 58 toNativeZipStream(handle)->setInput(env, buf, off, len); 59 } 60 61 static jint Inflater_setFileInputImpl(JNIEnv* env, jobject, jobject javaFileDescriptor, jlong off, jint len, jlong handle) { 62 NativeZipStream* stream = toNativeZipStream(handle); 63 64 // We reuse the existing native buffer if it's large enough. 65 // TODO: benchmark. 66 if (stream->inCap < len) { 67 stream->setInput(env, NULL, 0, len); 68 } else { 69 stream->stream.next_in = reinterpret_cast<Bytef*>(&stream->input[0]); 70 stream->stream.avail_in = len; 71 } 72 73 // As an Android-specific optimization, we read directly onto the native heap. 74 // The original code used Java to read onto the Java heap and then called setInput(byte[]). 75 // TODO: benchmark. 76 int fd = jniGetFDFromFileDescriptor(env, javaFileDescriptor); 77 int rc = TEMP_FAILURE_RETRY(lseek(fd, off, SEEK_SET)); 78 if (rc == -1) { 79 jniThrowIOException(env, errno); 80 return 0; 81 } 82 jint totalByteCount = 0; 83 Bytef* dst = reinterpret_cast<Bytef*>(&stream->input[0]); 84 ssize_t byteCount; 85 while ((byteCount = TEMP_FAILURE_RETRY(read(fd, dst, len))) > 0) { 86 dst += byteCount; 87 len -= byteCount; 88 totalByteCount += byteCount; 89 } 90 if (byteCount == -1) { 91 jniThrowIOException(env, errno); 92 return 0; 93 } 94 return totalByteCount; 95 } 96 97 static jint Inflater_inflateImpl(JNIEnv* env, jobject recv, jbyteArray buf, int off, int len, jlong handle) { 98 jfieldID fid2 = 0; 99 100 /* We need to get the number of bytes already read */ 101 jfieldID fid = gCachedFields.inRead; 102 jint inBytes = env->GetIntField(recv, fid); 103 104 NativeZipStream* stream = toNativeZipStream(handle); 105 stream->stream.avail_out = len; 106 jint sin = stream->stream.total_in; 107 jint sout = stream->stream.total_out; 108 ScopedByteArrayRW out(env, buf); 109 if (out.get() == NULL) { 110 return -1; 111 } 112 stream->stream.next_out = reinterpret_cast<Bytef*>(out.get() + off); 113 int err = inflate(&stream->stream, Z_SYNC_FLUSH); 114 if (err != Z_OK) { 115 if (err == Z_STREAM_ERROR) { 116 return 0; 117 } 118 if (err == Z_STREAM_END || err == Z_NEED_DICT) { 119 env->SetIntField(recv, fid, (jint) stream->stream.total_in - sin + inBytes); 120 if (err == Z_STREAM_END) { 121 fid2 = gCachedFields.finished; 122 } else { 123 fid2 = gCachedFields.needsDictionary; 124 } 125 env->SetBooleanField(recv, fid2, JNI_TRUE); 126 return stream->stream.total_out - sout; 127 } else { 128 throwExceptionForZlibError(env, "java/util/zip/DataFormatException", err); 129 return -1; 130 } 131 } 132 133 /* Need to update the number of input bytes read. Is there a better way 134 * (Maybe global the fid then delete when end is called)? 135 */ 136 env->SetIntField(recv, fid, (jint) stream->stream.total_in - sin + inBytes); 137 return stream->stream.total_out - sout; 138 } 139 140 static jint Inflater_getAdlerImpl(JNIEnv*, jobject, jlong handle) { 141 return toNativeZipStream(handle)->stream.adler; 142 } 143 144 static void Inflater_endImpl(JNIEnv*, jobject, jlong handle) { 145 NativeZipStream* stream = toNativeZipStream(handle); 146 inflateEnd(&stream->stream); 147 delete stream; 148 } 149 150 static void Inflater_setDictionaryImpl(JNIEnv* env, jobject, jbyteArray dict, int off, int len, jlong handle) { 151 toNativeZipStream(handle)->setDictionary(env, dict, off, len, true); 152 } 153 154 static void Inflater_resetImpl(JNIEnv* env, jobject, jlong handle) { 155 int err = inflateReset(&toNativeZipStream(handle)->stream); 156 if (err != Z_OK) { 157 throwExceptionForZlibError(env, "java/lang/IllegalArgumentException", err); 158 } 159 } 160 161 static jlong Inflater_getTotalOutImpl(JNIEnv*, jobject, jlong handle) { 162 return toNativeZipStream(handle)->stream.total_out; 163 } 164 165 static jlong Inflater_getTotalInImpl(JNIEnv*, jobject, jlong handle) { 166 return toNativeZipStream(handle)->stream.total_in; 167 } 168 169 static JNINativeMethod gMethods[] = { 170 NATIVE_METHOD(Inflater, createStream, "(Z)J"), 171 NATIVE_METHOD(Inflater, endImpl, "(J)V"), 172 NATIVE_METHOD(Inflater, getAdlerImpl, "(J)I"), 173 NATIVE_METHOD(Inflater, getTotalInImpl, "(J)J"), 174 NATIVE_METHOD(Inflater, getTotalOutImpl, "(J)J"), 175 NATIVE_METHOD(Inflater, inflateImpl, "([BIIJ)I"), 176 NATIVE_METHOD(Inflater, resetImpl, "(J)V"), 177 NATIVE_METHOD(Inflater, setDictionaryImpl, "([BIIJ)V"), 178 NATIVE_METHOD(Inflater, setFileInputImpl, "(Ljava/io/FileDescriptor;JIJ)I"), 179 NATIVE_METHOD(Inflater, setInputImpl, "([BIIJ)V"), 180 }; 181 int register_java_util_zip_Inflater(JNIEnv* env) { 182 gCachedFields.finished = env->GetFieldID(JniConstants::inflaterClass, "finished", "Z"); 183 gCachedFields.inRead = env->GetFieldID(JniConstants::inflaterClass, "inRead", "I"); 184 gCachedFields.needsDictionary = env->GetFieldID(JniConstants::inflaterClass, "needsDictionary", "Z"); 185 return jniRegisterNativeMethods(env, "java/util/zip/Inflater", gMethods, NELEM(gMethods)); 186 } 187