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 37 // ~ZipEntryRO() { 38 @Override finalize()39 protected void finalize() { 40 // EndIteration(cookie); 41 } 42 43 // private: 44 // ZipEntryRO(final ZipEntryRO& other); 45 // ZipEntryRO& operator=(final ZipEntryRO& other); 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 { 93 final Ref<ZipArchiveHandle> handle = new Ref<>(null); 94 final int error = OpenArchive(zipFileName, handle); 95 if (isTruthy(error)) { 96 ALOGW("Error opening archive %s: %s", zipFileName, ErrorCodeString(error)); 97 CloseArchive(); 98 return null; 99 } 100 101 return new ZipFileRO(handle.get(), zipFileName); 102 } 103 104 // /* static */ ZipFileRO* ZipFileRO::openFd(int fd, String debugFileName, 105 // boolean assume_ownership) 106 // { 107 // ZipArchiveHandle handle; 108 // int error = OpenArchiveFd(fd, debugFileName, &handle, assume_ownership); 109 // if (error) { 110 // ALOGW("Error opening archive fd %d %s: %s", fd, debugFileName, ErrorCodeString(error)); 111 // CloseArchive(handle); 112 // return NULL; 113 // } 114 // 115 // return new ZipFileRO(handle, strdup(debugFileName)); 116 // } 117 findEntryByName(final String entryName)118 org.robolectric.res.android.ZipFileRO.ZipEntryRO findEntryByName(final String entryName) 119 { 120 ZipEntryRO data = new ZipEntryRO(); 121 data.name = String(entryName); 122 123 if (mHandle.dataOffsets.get(entryName) == null) { 124 return null; 125 } 126 data.dataOffset = mHandle.dataOffsets.get(entryName); 127 128 final Ref<ZipEntry> zipEntryRef = new Ref<>(data.entry); 129 final int error = FindEntry(mHandle, data.name, zipEntryRef); 130 if (isTruthy(error)) { 131 return null; 132 } 133 134 data.entry = zipEntryRef.get(); 135 return data; 136 } 137 138 /* 139 * Get the useful fields from the zip entry. 140 * 141 * Returns "false" if the offsets to the fields or the contents of the fields 142 * appear to be bogus. 143 */ 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)144 boolean getEntryInfo(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Ref<Short> pMethod, 145 final Ref<Long> pUncompLen, Ref<Long> pCompLen, Ref<Long> pOffset, 146 final Ref<Long> pModWhen, Ref<Long> pCrc32) 147 { 148 final ZipEntryRO zipEntry = /*reinterpret_cast<ZipEntryRO*>*/ entry; 149 final ZipEntry ze = zipEntry.entry; 150 151 if (pMethod != null) { 152 pMethod.set((short) ze.getMethod()); 153 } 154 if (pUncompLen != null) { 155 pUncompLen.set(ze.getSize()); // uncompressed_length 156 } 157 if (pCompLen != null) { 158 pCompLen.set(ze.getCompressedSize()); 159 } 160 if (pOffset != null) { 161 throw new UnsupportedOperationException("Figure out offset"); 162 // pOffset = ze.offset; 163 } 164 if (pModWhen != null) { 165 // todo pModWhen.set(ze.getLastModifiedTime().toMillis()); 166 } 167 if (pCrc32 != null) { 168 pCrc32.set(ze.getCrc()); 169 } 170 171 return true; 172 } 173 startIteration(Ref<Enumeration<? extends ZipEntry>> cookie)174 boolean startIteration(Ref<Enumeration<? extends ZipEntry>> cookie) { 175 return startIteration(cookie, null, null); 176 } 177 startIteration( Ref<Enumeration<? extends ZipEntry>> cookie, final String prefix, final String suffix)178 boolean startIteration(/* void** */ Ref<Enumeration<? extends ZipEntry>> cookie, final String prefix, final String suffix) 179 { 180 cookie.set(this.mHandle.zipFile.entries()); 181 // ZipEntryRO* ze = new ZipEntryRO; 182 // String pe(prefix ? prefix : ""); 183 // String se(suffix ? suffix : ""); 184 // int error = StartIteration(mHandle, &(ze.cookie), 185 // prefix ? &pe : null, 186 // suffix ? &se : null); 187 // if (error) { 188 // ALOGW("Could not start iteration over %s: %s", mFileName, ErrorCodeString(error)); 189 // delete ze; 190 // return false; 191 // } 192 // 193 // *cookie = ze; 194 return true; 195 } 196 nextEntry( Enumeration<? extends ZipEntry> cookie)197 org.robolectric.res.android.ZipFileRO.ZipEntryRO nextEntry(/*void* */ Enumeration<? extends ZipEntry> cookie) 198 { 199 if (!cookie.hasMoreElements()) { 200 return null; 201 } 202 ZipEntryRO zipEntryRO = new ZipEntryRO(); 203 zipEntryRO.entry = cookie.nextElement(); 204 return zipEntryRO; 205 // ZipEntryRO ze = /*reinterpret_cast<ZipEntryRO*>*/(ZipEntryRO) cookie; 206 // int error = Next(ze.cookie, &(ze.entry), &(ze.name)); 207 // if (error) { 208 // if (error != -1) { 209 // ALOGW("Error iteration over %s: %s", mFileName, ErrorCodeString(error)); 210 // } 211 // return null; 212 // } 213 // 214 // return &(ze.entry); 215 } 216 endIteration( Object cookie)217 void endIteration(/*void**/ Object cookie) 218 { 219 // delete reinterpret_cast<ZipEntryRO*>(cookie); 220 } 221 releaseEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry)222 void releaseEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry) 223 { 224 // delete reinterpret_cast<ZipEntryRO*>(entry); 225 } 226 227 /* 228 * Copy the entry's filename to the buffer. 229 */ getEntryFileName(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Ref<String> buffer)230 int getEntryFileName(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Ref<String> buffer) 231 { 232 buffer.set(entry.entry.getName()); 233 234 // final ZipEntryRO* zipEntry = reinterpret_cast<ZipEntryRO*>(entry); 235 // final uint16_t requiredSize = zipEntry.name.name_length + 1; 236 // 237 // if (bufLen < requiredSize) { 238 // ALOGW("Buffer too short, requires %d bytes for entry name", requiredSize); 239 // return requiredSize; 240 // } 241 // 242 // memcpy(buffer, zipEntry.name.name, requiredSize - 1); 243 // buffer[requiredSize - 1] = '\0'; 244 // 245 return 0; 246 } 247 248 /* 249 * Create a new FileMap object that spans the data in "entry". 250 */ ZipFileRO(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry)251 /*FileMap*/ ZipFileRO(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry) 252 { 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 { 280 // final _ZipEntryRO *zipEntry = reinterpret_cast<_ZipEntryRO*>(entry); 281 // const ZipEntry& ze = zipEntry->entry; 282 // int fd = GetFileDescriptor(mHandle); 283 284 FileMap newMap = new FileMap(); 285 if (!newMap.createFromZip( 286 mFileName, 287 mHandle.zipFile, 288 entry.entry, 289 entry.dataOffset, 290 toIntExact(entry.entry.getCompressedSize()), 291 true)) { 292 // delete newMap; 293 return null; 294 } 295 296 return newMap; 297 } 298 299 /* 300 * Uncompress an entry, in its entirety, into the provided output buffer. 301 * 302 * This doesn't verify the data's CRC, which might be useful for 303 * uncompressed data. The caller should be able to manage it. 304 */ uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Object buffer, int size)305 boolean uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, Object buffer, int size) 306 { 307 throw new UnsupportedOperationException("Implememnt me"); 308 // ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry); 309 // final int error = ExtractToMemory(mHandle, &(zipEntry.entry), 310 // (uint8_t*) buffer, size); 311 // if (error) { 312 // ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error)); 313 // return false; 314 // } 315 // 316 // return true; 317 } 318 319 /* 320 * Uncompress an entry, in its entirety, to an open file descriptor. 321 * 322 * This doesn't verify the data's CRC, but probably should. 323 */ uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, int fd)324 boolean uncompressEntry(org.robolectric.res.android.ZipFileRO.ZipEntryRO entry, int fd) 325 { 326 throw new UnsupportedOperationException("Implememnt me"); 327 // ZipEntryRO *zipEntry = reinterpret_cast<ZipEntryRO*>(entry); 328 // final int error = ExtractEntryToFile(mHandle, &(zipEntry.entry), fd); 329 // if (error) { 330 // ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error)); 331 // return false; 332 // } 333 // 334 // return true; 335 } 336 String(String string)337 static String String(String string) { 338 return string; 339 } 340 } 341