1 package org.robolectric.res.android; 2 3 import static org.robolectric.res.android.Asset.toIntExact; 4 import static org.robolectric.res.android.Errors.NAME_NOT_FOUND; 5 import static org.robolectric.res.android.Errors.NO_ERROR; 6 import static org.robolectric.res.android.Util.ALOGW; 7 import static org.robolectric.res.android.Util.isTruthy; 8 9 import java.io.File; 10 import java.io.IOException; 11 import java.util.Enumeration; 12 import java.util.zip.ZipEntry; 13 import java.util.zip.ZipFile; 14 15 public class ZipFileRO { 16 17 static final int kCompressStored = 0; 18 static final int kCompressDeflated = 8; 19 20 final ZipArchiveHandle mHandle; 21 final String mFileName; 22 ZipFileRO(ZipArchiveHandle handle, String fileName)23 ZipFileRO(ZipArchiveHandle handle, String fileName) { 24 this.mHandle = handle; 25 this.mFileName = fileName; 26 } 27 28 static class ZipEntryRO { 29 ZipEntry entry; 30 String name; 31 long dataOffset; 32 Object cookie; 33 ZipEntryRO()34 ZipEntryRO() {} 35 36 // ~ZipEntryRO() { 37 @Override finalize()38 protected void finalize() { 39 // EndIteration(cookie); 40 } 41 42 // private: 43 // ZipEntryRO(final ZipEntryRO& other); 44 // ZipEntryRO& operator=(final ZipEntryRO& other); 45 } 46 ; 47 48 // ~ZipFileRO() { 49 @Override finalize()50 protected void finalize() { 51 CloseArchive(); 52 // free(mFileName); 53 } 54 OpenArchive(String zipFileName, Ref<ZipArchiveHandle> mHandle)55 static int OpenArchive(String zipFileName, Ref<ZipArchiveHandle> mHandle) { 56 try { 57 File file = new File(zipFileName); 58 // TODO: consider moving away from ZipFile. By using ZipFile and guessDataOffsets, the zip 59 // central directory is being read twice 60 ZipFile zipFile = new ZipFile(file); 61 mHandle.set( 62 new ZipArchiveHandle(zipFile, FileMap.guessDataOffsets(file, (int) file.length()))); 63 return NO_ERROR; 64 } catch (IOException e) { 65 return NAME_NOT_FOUND; 66 } 67 } 68 CloseArchive()69 private static void CloseArchive() { 70 throw new UnsupportedOperationException(); 71 } 72 ErrorCodeString(int error)73 private static String ErrorCodeString(int error) { 74 return "error " + error; 75 } 76 FindEntry(ZipArchiveHandle mHandle, String name, Ref<ZipEntry> zipEntryRef)77 static int FindEntry(ZipArchiveHandle mHandle, String name, Ref<ZipEntry> zipEntryRef) { 78 ZipEntry entry = mHandle.zipFile.getEntry(name); 79 zipEntryRef.set(entry); 80 if (entry == null) { 81 return NAME_NOT_FOUND; 82 } 83 return NO_ERROR; 84 } 85 86 /* 87 * Open the specified file read-only. We memory-map the entire thing and 88 * close the file before returning. 89 */ 90 /* static */ open(final String zipFileName)91 static ZipFileRO open(final String zipFileName) { 92 final Ref<ZipArchiveHandle> handle = new Ref<>(null); 93 final int error = OpenArchive(zipFileName, handle); 94 if (isTruthy(error)) { 95 ALOGW("Error opening archive %s: %s", zipFileName, ErrorCodeString(error)); 96 CloseArchive(); 97 return null; 98 } 99 100 return new ZipFileRO(handle.get(), zipFileName); 101 } 102 103 // /* static */ ZipFileRO* ZipFileRO::openFd(int fd, String debugFileName, 104 // boolean assume_ownership) 105 // { 106 // ZipArchiveHandle handle; 107 // int error = OpenArchiveFd(fd, debugFileName, &handle, assume_ownership); 108 // if (error) { 109 // ALOGW("Error opening archive fd %d %s: %s", fd, debugFileName, ErrorCodeString(error)); 110 // CloseArchive(handle); 111 // return NULL; 112 // } 113 // 114 // return new ZipFileRO(handle, strdup(debugFileName)); 115 // } 116 findEntryByName(final String entryName)117 org.robolectric.res.android.ZipFileRO.ZipEntryRO findEntryByName(final String entryName) { 118 ZipEntryRO data = new ZipEntryRO(); 119 data.name = String(entryName); 120 121 if (mHandle.dataOffsets.get(entryName) == null) { 122 return null; 123 } 124 data.dataOffset = mHandle.dataOffsets.get(entryName); 125 126 final Ref<ZipEntry> zipEntryRef = new Ref<>(data.entry); 127 final int error = FindEntry(mHandle, data.name, zipEntryRef); 128 if (isTruthy(error)) { 129 return null; 130 } 131 132 data.entry = zipEntryRef.get(); 133 return data; 134 } 135 136 /* 137 * Get the useful fields from the zip entry. 138 * 139 * Returns "false" if the offsets to the fields or the contents of the fields 140 * appear to be bogus. 141 */ getEntryInfo( org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Ref<Short> pMethod, final Ref<Long> pUncompLen, Ref<Long> pCompLen, Ref<Long> pOffset, final Ref<Long> pModWhen, Ref<Long> pCrc32)142 boolean getEntryInfo( 143 org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, 144 Ref<Short> pMethod, 145 final Ref<Long> pUncompLen, 146 Ref<Long> pCompLen, 147 Ref<Long> pOffset, 148 final Ref<Long> pModWhen, 149 Ref<Long> pCrc32) { 150 final ZipEntryRO zipEntry = /*reinterpret_cast<ZipEntryRO*>*/ entry; 151 final ZipEntry ze = zipEntry.entry; 152 153 if (pMethod != null) { 154 pMethod.set((short) ze.getMethod()); 155 } 156 if (pUncompLen != null) { 157 pUncompLen.set(ze.getSize()); // uncompressed_length 158 } 159 if (pCompLen != null) { 160 pCompLen.set(ze.getCompressedSize()); 161 } 162 if (pOffset != null) { 163 throw new UnsupportedOperationException("Figure out offset"); 164 // pOffset = ze.offset; 165 } 166 if (pModWhen != null) { 167 // todo pModWhen.set(ze.getLastModifiedTime().toMillis()); 168 } 169 if (pCrc32 != null) { 170 pCrc32.set(ze.getCrc()); 171 } 172 173 return true; 174 } 175 startIteration(Ref<Enumeration<? extends ZipEntry>> cookie)176 boolean startIteration(Ref<Enumeration<? extends ZipEntry>> cookie) { 177 return startIteration(cookie, null, null); 178 } 179 startIteration( Ref<Enumeration<? extends ZipEntry>> cookie, final String prefix, final String suffix)180 boolean startIteration( 181 /* void** */ Ref<Enumeration<? extends ZipEntry>> cookie, 182 final String prefix, 183 final String suffix) { 184 cookie.set(this.mHandle.zipFile.entries()); 185 // ZipEntryRO* ze = new ZipEntryRO; 186 // String pe(prefix ? prefix : ""); 187 // String se(suffix ? suffix : ""); 188 // int error = StartIteration(mHandle, &(ze.cookie), 189 // prefix ? &pe : null, 190 // suffix ? &se : null); 191 // if (error) { 192 // ALOGW("Could not start iteration over %s: %s", mFileName, ErrorCodeString(error)); 193 // delete ze; 194 // return false; 195 // } 196 // 197 // *cookie = ze; 198 return true; 199 } 200 nextEntry( Enumeration<? extends ZipEntry> cookie)201 org.robolectric.res.android.ZipFileRO.ZipEntryRO nextEntry( 202 /*void* */ Enumeration<? extends ZipEntry> cookie) { 203 if (!cookie.hasMoreElements()) { 204 return null; 205 } 206 ZipEntryRO zipEntryRO = new ZipEntryRO(); 207 zipEntryRO.entry = cookie.nextElement(); 208 return zipEntryRO; 209 // ZipEntryRO ze = /*reinterpret_cast<ZipEntryRO*>*/(ZipEntryRO) cookie; 210 // int error = Next(ze.cookie, &(ze.entry), &(ze.name)); 211 // if (error) { 212 // if (error != -1) { 213 // ALOGW("Error iteration over %s: %s", mFileName, ErrorCodeString(error)); 214 // } 215 // return null; 216 // } 217 // 218 // return &(ze.entry); 219 } 220 endIteration( Object cookie)221 void endIteration(/*void**/ Object cookie) { 222 // delete reinterpret_cast<ZipEntryRO*>(cookie); 223 } 224 releaseEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry)225 void releaseEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry) { 226 // delete reinterpret_cast<ZipEntryRO*>(entry); 227 } 228 229 /* 230 * Copy the entry's filename to the buffer. 231 */ getEntryFileName(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Ref<String> buffer)232 int getEntryFileName(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Ref<String> buffer) { 233 buffer.set(entry.entry.getName()); 234 235 // final ZipEntryRO* zipEntry = reinterpret_cast<ZipEntryRO*>(entry); 236 // final uint16_t requiredSize = zipEntry.name.name_length + 1; 237 // 238 // if (bufLen < requiredSize) { 239 // ALOGW("Buffer too short, requires %d bytes for entry name", requiredSize); 240 // return requiredSize; 241 // } 242 // 243 // memcpy(buffer, zipEntry.name.name, requiredSize - 1); 244 // buffer[requiredSize - 1] = '\0'; 245 // 246 return 0; 247 } 248 249 /* 250 * Create a new FileMap object that spans the data in "entry". 251 */ ZipFileRO(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry)252 /*FileMap*/ ZipFileRO(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry) { 253 throw new UnsupportedOperationException("Implememnt me"); 254 255 // final ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry); 256 // final ZipEntry& ze = zipEntry.entry; 257 // int fd = GetFileDescriptor(mHandle); 258 // size_t actualLen = 0; 259 // 260 // if (ze.method == kCompressStored) { 261 // actualLen = ze.uncompressed_length; 262 // } else { 263 // actualLen = ze.compressed_length; 264 // } 265 // 266 // FileMap* newMap = new FileMap(); 267 // if (!newMap.create(mFileName, fd, ze.offset, actualLen, true)) { 268 // delete newMap; 269 // return null; 270 // } 271 // 272 // return newMap; 273 } 274 275 /* 276 * Create a new FileMap object that spans the data in "entry". 277 */ createEntryFileMap(ZipEntryRO entry)278 FileMap createEntryFileMap(ZipEntryRO entry) { 279 // final _ZipEntryRO *zipEntry = reinterpret_cast<_ZipEntryRO*>(entry); 280 // const ZipEntry& ze = zipEntry->entry; 281 // int fd = GetFileDescriptor(mHandle); 282 283 FileMap newMap = new FileMap(); 284 if (!newMap.createFromZip( 285 mFileName, 286 mHandle.zipFile, 287 entry.entry, 288 entry.dataOffset, 289 toIntExact(entry.entry.getCompressedSize()), 290 true)) { 291 // delete newMap; 292 return null; 293 } 294 295 return newMap; 296 } 297 298 /* 299 * Uncompress an entry, in its entirety, into the provided output buffer. 300 * 301 * This doesn't verify the data's CRC, which might be useful for 302 * uncompressed data. The caller should be able to manage it. 303 */ uncompressEntry( org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Object buffer, int size)304 boolean uncompressEntry( 305 org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Object buffer, int size) { 306 throw new UnsupportedOperationException("Implememnt me"); 307 // ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry); 308 // final int error = ExtractToMemory(mHandle, &(zipEntry.entry), 309 // (uint8_t*) buffer, size); 310 // if (error) { 311 // ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error)); 312 // return false; 313 // } 314 // 315 // return true; 316 } 317 318 /* 319 * Uncompress an entry, in its entirety, to an open file descriptor. 320 * 321 * This doesn't verify the data's CRC, but probably should. 322 */ uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, int fd)323 boolean uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, int fd) { 324 throw new UnsupportedOperationException("Implememnt me"); 325 // ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry); 326 // final int error = ExtractEntryToFile(mHandle, &(zipEntry.entry), fd); 327 // if (error) { 328 // ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error)); 329 // return false; 330 // } 331 // 332 // return true; 333 } 334 String(String string)335 static String String(String string) { 336 return string; 337 } 338 } 339