Bug 781153 - ArchiveReader doesn't support zip-in-zip archives, r=jst
authorAndrea Marchesini <baku@mozilla.com>
Fri, 17 Aug 2012 18:04:03 -0700
changeset 102693 f450fe554bd686c9fd7bc7a3ca870b887db3c620
parent 102692 07d5886658b2a4f48f5325c363512636e5cd4067
child 102694 53951ff066a676c89cca4ffa46ab6924546706a9
push id23303
push userryanvm@gmail.com
push dateSat, 18 Aug 2012 11:22:19 +0000
treeherdermozilla-central@9c48df21d744 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst
bugs781153
milestone17.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 781153 - ArchiveReader doesn't support zip-in-zip archives, r=jst
dom/file/ArchiveEvent.cpp
dom/file/ArchiveRequest.cpp
dom/file/ArchiveZipEvent.cpp
dom/file/ArchiveZipFile.cpp
dom/file/test/Makefile.in
dom/file/test/test_archivereader_zip_in_zip.html
--- a/dom/file/ArchiveEvent.cpp
+++ b/dom/file/ArchiveEvent.cpp
@@ -94,17 +94,17 @@ ArchiveReaderEvent::ShareMainThread()
 
       PRInt32 offset = item->GetFilename().RFindChar('.');
       if (offset != kNotFound) {
         nsCString ext(item->GetFilename());
         ext.Cut(0, offset + 1);
 
         // Just to be sure, if something goes wrong, the mimetype is an empty string:
         nsCString type;
-        if (GetType(ext, type) == NS_OK)
+        if (NS_SUCCEEDED(GetType(ext, type)))
           item->SetType(type);
       }
 
       // This is a nsDOMFile:
       nsRefPtr<nsIDOMFile> file = item->File(mArchiveReader);
       fileList.AppendElement(file);
     }
   }
--- a/dom/file/ArchiveRequest.cpp
+++ b/dom/file/ArchiveRequest.cpp
@@ -106,17 +106,17 @@ ArchiveRequest::OpGetFile(const nsAStrin
   mOperation = GetFile;
   mFilename = aFilename;
 }
 
 nsresult
 ArchiveRequest::ReaderReady(nsTArray<nsCOMPtr<nsIDOMFile> >& aFileList,
                             nsresult aStatus)
 {
-  if (aStatus != NS_OK) {
+  if (NS_FAILED(aStatus)) {
     FireError(aStatus);
     return NS_OK;
   }
 
   jsval result;
   nsresult rv;
 
   nsIScriptContext* sc = GetContextForEventHandlers(&rv);
@@ -178,17 +178,17 @@ ArchiveRequest::GetFilenamesResult(JSCon
     rv = file->GetName(filename);
     NS_ENSURE_SUCCESS(rv, rv);
 
     JSString* str = JS_NewUCStringCopyZ(aCx, filename.get());
     NS_ENSURE_TRUE(str, NS_ERROR_OUT_OF_MEMORY);
 
     jsval item = STRING_TO_JSVAL(str);
 
-    if (rv != NS_OK || !JS_SetElement(aCx, array, i, &item)) {
+    if (NS_FAILED(rv) || !JS_SetElement(aCx, array, i, &item)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   if (!JS_FreezeObject(aCx, array)) {
     return NS_ERROR_FAILURE;
   }
   
--- a/dom/file/ArchiveZipEvent.cpp
+++ b/dom/file/ArchiveZipEvent.cpp
@@ -81,43 +81,43 @@ ArchiveReaderZipEvent::ArchiveReaderZipE
 nsresult
 ArchiveReaderZipEvent::Exec()
 {
   PRUint32 centralOffset(0);
   nsresult rv;
 
   nsCOMPtr<nsIInputStream> inputStream;
   rv = mArchiveReader->GetInputStream(getter_AddRefs(inputStream));
-  if (rv != NS_OK || !inputStream) {
+  if (NS_FAILED(rv) || !inputStream) {
     return RunShare(NS_ERROR_UNEXPECTED);
   }
 
   // From the input stream to a seekable stream
   nsCOMPtr<nsISeekableStream> seekableStream;
   seekableStream = do_QueryInterface(inputStream);
   if (!seekableStream) {
     return RunShare(NS_ERROR_UNEXPECTED);
   }
 
   PRUint64 size;
   rv = mArchiveReader->GetSize(&size);
-  if (rv != NS_OK) {
+  if (NS_FAILED(rv)) {
     return RunShare(NS_ERROR_UNEXPECTED);
   }
 
   // Reading backward.. looking for the ZipEnd signature
   for (PRUint64 curr = size - ZIPEND_SIZE; curr > 4; --curr)
   {
     seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, curr);
 
     PRUint8 buffer[ZIPEND_SIZE];
     PRUint32 ret;
 
     rv = inputStream->Read((char*)buffer, sizeof(buffer), &ret);
-    if (rv != NS_OK || ret != sizeof(buffer)) {
+    if (NS_FAILED(rv) || ret != sizeof(buffer)) {
       return RunShare(NS_ERROR_UNEXPECTED);
     }
 
     // Here we are:
     if (ArchiveZipItem::StrToInt32(buffer) == ENDSIG) {
       centralOffset = ArchiveZipItem::StrToInt32(((ZipEnd*)buffer)->offset_central_dir);
       break;
     }
@@ -132,34 +132,34 @@ ArchiveReaderZipEvent::Exec()
   seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, centralOffset);
 
   // For each central directory:
   while (centralOffset <= size - ZIPCENTRAL_SIZE) {
     ZipCentral centralStruct;
     PRUint32 ret;
     
     rv = inputStream->Read((char*)&centralStruct, ZIPCENTRAL_SIZE, &ret);
-    if (rv != NS_OK || ret != ZIPCENTRAL_SIZE) {
+    if (NS_FAILED(rv) || ret != ZIPCENTRAL_SIZE) {
       return RunShare(NS_ERROR_UNEXPECTED);
     }
 
     PRUint16 filenameLen = ArchiveZipItem::StrToInt16(centralStruct.filename_len);
     PRUint16 extraLen = ArchiveZipItem::StrToInt16(centralStruct.extrafield_len);
     PRUint16 commentLen = ArchiveZipItem::StrToInt16(centralStruct.commentfield_len);
 
     // Point to the next item at the top of loop
     centralOffset += ZIPCENTRAL_SIZE + filenameLen + extraLen + commentLen;
     if (filenameLen == 0 || filenameLen >= PATH_MAX || centralOffset >= size) {
       return RunShare(NS_ERROR_FILE_CORRUPTED);
     }
 
     // Read the name:
     char* filename = (char*)PR_Malloc(filenameLen + 1);
     rv = inputStream->Read(filename, filenameLen, &ret);
-    if (rv != NS_OK || ret != filenameLen) {
+    if (NS_FAILED(rv) || ret != filenameLen) {
       return RunShare(NS_ERROR_UNEXPECTED);
     }
 
     filename[filenameLen] = 0;
 
     // We ignore the directories:
     if (filename[filenameLen - 1] != '/') {
       mFileList.AppendElement(new ArchiveZipItem(filename, centralStruct));
--- a/dom/file/ArchiveZipFile.cpp
+++ b/dom/file/ArchiveZipFile.cpp
@@ -12,151 +12,143 @@
 #include "mozilla/Attributes.h"
 
 USING_FILE_NAMESPACE
 
 #define ZIP_CHUNK 16384
 
 // a internat input stream object
 
-class ArchiveInputStream MOZ_FINAL : public nsIInputStream
+class ArchiveInputStream MOZ_FINAL : public nsIInputStream,
+                                     public nsISeekableStream
 {
 public:
-  ArchiveInputStream(ArchiveReader* aReader,
+  ArchiveInputStream(PRUint64 aParentSize,
+                     nsIInputStream* aInputStream,
                      nsString& aFilename,
                      PRUint32 aStart,
                      PRUint32 aLength,
                      ZipCentral& aCentral)
-  : mArchiveReader(aReader),
-    mCentral(aCentral),
+  : mCentral(aCentral),
     mFilename(aFilename),
     mStart(aStart),
     mLength(aLength),
     mStatus(NotStarted)
   {
     MOZ_COUNT_CTOR(ArchiveInputStream);
+
+    // Reset the data:
+    memset(&mData, 0, sizeof(mData));
+
+    mData.parentSize = aParentSize;
+    mData.inputStream = aInputStream;
   }
 
   virtual ~ArchiveInputStream()
   {
     MOZ_COUNT_DTOR(ArchiveInputStream);
     Close();
   }
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIINPUTSTREAM
+  NS_DECL_NSISEEKABLESTREAM
 
 private:
   nsresult Init();
 
 private: // data
-  nsRefPtr<ArchiveReader> mArchiveReader;
   ZipCentral mCentral;
   nsString mFilename;
   PRUint32 mStart;
   PRUint32 mLength;
 
   z_stream mZs;
 
   enum {
     NotStarted,
     Started,
     Done
   } mStatus;
 
   struct {
+    PRUint64 parentSize;
     nsCOMPtr<nsIInputStream> inputStream;
+
     unsigned char input[ZIP_CHUNK];
     PRUint32 sizeToBeRead;
+    PRUint32 cursor;
+
     bool compressed; // a zip file can contain stored or compressed files
   } mData;
 };
 
-NS_IMPL_THREADSAFE_ISUPPORTS1(ArchiveInputStream, nsIInputStream)
+NS_IMPL_THREADSAFE_ISUPPORTS2(ArchiveInputStream,
+                              nsIInputStream,
+                              nsISeekableStream)
 
 nsresult
 ArchiveInputStream::Init()
 {
   nsresult rv;
 
   memset(&mZs, 0, sizeof(z_stream));
   int zerr = inflateInit2(&mZs, -MAX_WBITS);
   if (zerr != Z_OK)
     return NS_ERROR_OUT_OF_MEMORY;
 
-  // Reset the data:
-  memset(&mData, 0, sizeof(mData));
   mData.sizeToBeRead = ArchiveZipItem::StrToInt32(mCentral.size);
 
   PRUint32 offset = ArchiveZipItem::StrToInt32(mCentral.localhdr_offset);
-  PRUint64 size;
-  rv = mArchiveReader->GetSize(&size);
-  NS_ENSURE_SUCCESS(rv, rv);
-  
+
   // The file is corrupt
-  if (offset + ZIPLOCAL_SIZE > size)
-    return NS_ERROR_UNEXPECTED;
-
-  mArchiveReader->GetInputStream(getter_AddRefs(mData.inputStream));
-  if (rv != NS_OK || !mData.inputStream)
+  if (offset + ZIPLOCAL_SIZE > mData.parentSize)
     return NS_ERROR_UNEXPECTED;
 
   // From the input stream to a seekable stream
   nsCOMPtr<nsISeekableStream> seekableStream;
   seekableStream = do_QueryInterface(mData.inputStream);
   if (!seekableStream)
     return NS_ERROR_UNEXPECTED;
 
   // Seek + read the ZipLocal struct
   seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, offset);
   PRUint8 buffer[ZIPLOCAL_SIZE];
   PRUint32 ret;
 
   rv = mData.inputStream->Read((char*)buffer, ZIPLOCAL_SIZE, &ret);
-  if (rv != NS_OK || ret != ZIPLOCAL_SIZE)
+  if (NS_FAILED(rv) || ret != ZIPLOCAL_SIZE)
     return NS_ERROR_UNEXPECTED;
 
   // Signature check:
   if (ArchiveZipItem::StrToInt32(buffer) != LOCALSIG)
     return NS_ERROR_UNEXPECTED;
 
   ZipLocal local;
   memcpy(&local, buffer, ZIPLOCAL_SIZE);
 
   // Seek to the real data:
   offset += ZIPLOCAL_SIZE +
             ArchiveZipItem::StrToInt16(local.filename_len) +
             ArchiveZipItem::StrToInt16(local.extrafield_len);
 
   // The file is corrupt if there is not enough data
-  if (offset + mData.sizeToBeRead > size)
+  if (offset + mData.sizeToBeRead > mData.parentSize)
     return NS_ERROR_UNEXPECTED;
 
   // Data starts here:
   seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, offset);
 
   // The file is compressed or not?
   mData.compressed = (ArchiveZipItem::StrToInt16(mCentral.method) != 0);
 
   // We have to skip the first mStart bytes:
   if (mStart != 0) {
-    PRUint32 done(mStart);
-    PRUint32 ret;
-    char buffer[1024];
-
-    while (done > 0) {
-      rv = Read(buffer, done > sizeof(buffer) ? sizeof(buffer) : done, &ret);
-      if (rv != NS_OK)
-        return rv;
-
-      if (ret == 0)
-        return NS_ERROR_UNEXPECTED;
-
-      done -= ret;
-    }
+    rv = Seek(NS_SEEK_SET, mStart);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArchiveInputStream::Close()
 {
@@ -166,17 +158,17 @@ ArchiveInputStream::Close()
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArchiveInputStream::Available(PRUint64* _retval)
 {
-  *_retval = mLength - mZs.total_out - mStart;
+  *_retval = mLength - mData.cursor - mStart;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArchiveInputStream::Read(char* aBuffer,
                          PRUint32 aCount,
                          PRUint32* _retval)
 {
@@ -185,17 +177,17 @@ ArchiveInputStream::Read(char* aBuffer,
 
   nsresult rv;
 
   // This is the first time:
   if (mStatus == NotStarted) {
     mStatus = Started;
 
     rv = Init();
-    if (rv != NS_OK)
+    if (NS_FAILED(rv))
       return rv;
 
     // Let's set avail_out to -1 so we read something from the stream.
     mZs.avail_out = (uInt)-1;
   }
 
   // Nothing more can be read
   if (mStatus == Done) {
@@ -203,37 +195,38 @@ ArchiveInputStream::Read(char* aBuffer,
     return NS_OK;
   }
 
   // Stored file:
   if (!mData.compressed)
   {
     rv = mData.inputStream->Read(aBuffer,
                                  (mData.sizeToBeRead > aCount ?
-                                      aCount : mData.sizeToBeRead),
+                                    aCount : mData.sizeToBeRead),
                                  _retval);
-    if (rv == NS_OK) {
+    if (NS_SUCCEEDED(rv)) {
       mData.sizeToBeRead -= *_retval;
+      mData.cursor += *_retval;
 
       if (mData.sizeToBeRead == 0)
         mStatus = Done;
     }
 
     return rv;
   }
 
   // We have nothing ready to be processed:
   if (mZs.avail_out != 0 && mData.sizeToBeRead != 0)
   {
     PRUint32 ret;
     rv = mData.inputStream->Read((char*)mData.input,
                                  (mData.sizeToBeRead > sizeof(mData.input) ?
                                       sizeof(mData.input) : mData.sizeToBeRead),
                                  &ret);
-    if (rv != NS_OK)
+    if (NS_FAILED(rv))
       return rv;
 
     // Terminator:
     if (ret == 0) {
       *_retval = 0;
       return NS_OK;
     }
 
@@ -248,16 +241,17 @@ ArchiveInputStream::Read(char* aBuffer,
   int ret = inflate(&mZs, mData.sizeToBeRead ? Z_NO_FLUSH : Z_FINISH);
   if (ret != Z_BUF_ERROR && ret != Z_OK && ret != Z_STREAM_END)
     return NS_ERROR_UNEXPECTED;
 
   if (ret == Z_STREAM_END)
     mStatus = Done;
 
   *_retval = aCount - mZs.avail_out;
+  mData.cursor += *_retval;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ArchiveInputStream::ReadSegments(nsWriteSegmentFun aWriter,
                                  void* aClosure,
                                  PRUint32 aCount,
                                  PRUint32* _retval)
@@ -270,26 +264,106 @@ ArchiveInputStream::ReadSegments(nsWrite
 NS_IMETHODIMP
 ArchiveInputStream::IsNonBlocking(bool* _retval)
 {
   // We are blocking
   *_retval = false;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+ArchiveInputStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
+{
+  PRInt64 pos = aOffset;
+
+  switch (aWhence) {
+  case NS_SEEK_SET:
+    break;
+
+  case NS_SEEK_CUR:
+    pos += mData.cursor;
+    break;
+
+  case NS_SEEK_END:
+    pos += mLength;
+    break;
+
+  default:
+    NS_NOTREACHED("unexpected whence value");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (pos == PRInt64(mData.cursor))
+    return NS_OK;
+
+  if (pos < 0 || pos >= mLength)
+    return NS_ERROR_FAILURE;
+
+  // We have to terminate the previous operation:
+  nsresult rv;
+  if (mStatus != NotStarted) {
+    rv = Close();
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  // Reset the cursor:
+  mData.cursor = 0;
+
+  // Note: This code is heavy but inflate does not have any seek() support:
+  PRUint32 ret;
+  char buffer[1024];
+  while (pos > 0) {
+    rv = Read(buffer, pos > PRInt64(sizeof(buffer)) ? sizeof(buffer) : pos, &ret);
+    if (NS_FAILED(rv))
+      return rv;
+
+    if (ret == 0)
+      return NS_ERROR_UNEXPECTED;
+
+    pos -= ret;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::Tell(PRInt64 *aResult)
+{
+  if (NS_FAILED(mStatus))
+    return mStatus;
+
+  LL_UI2L(*aResult, mData.cursor);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ArchiveInputStream::SetEOF()
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
 
 // ArchiveZipFile
 
 NS_IMETHODIMP
 ArchiveZipFile::GetInternalStream(nsIInputStream** aStream)
 {
   if (mLength > PR_INT32_MAX)
     return NS_ERROR_FAILURE;
 
-  nsRefPtr<ArchiveInputStream> stream = new ArchiveInputStream(mArchiveReader,
+  PRUint64 size;
+  nsresult rv = mArchiveReader->GetSize(&size);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIInputStream> inputStream;
+  rv = mArchiveReader->GetInputStream(getter_AddRefs(inputStream));
+  if (NS_FAILED(rv) || !inputStream)
+    return NS_ERROR_UNEXPECTED;
+
+  nsRefPtr<ArchiveInputStream> stream = new ArchiveInputStream(size,
+                                                               inputStream,
                                                                mFilename,
                                                                mStart,
                                                                mLength,
                                                                mCentral);
   NS_ADDREF(stream);
 
   *aStream = stream;
   return NS_OK;
--- a/dom/file/test/Makefile.in
+++ b/dom/file/test/Makefile.in
@@ -25,11 +25,12 @@ MOCHITEST_FILES = \
   test_readonly_lockedfiles.html \
   test_request_readyState.html \
   test_stream_tracking.html \
   test_success_events_after_abort.html \
   test_truncate.html \
   test_write_read_data.html \
   test_workers.html \
   test_archivereader.html \
+  test_archivereader_zip_in_zip.html \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/file/test/test_archivereader_zip_in_zip.html
@@ -0,0 +1,128 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Archive Reader Zip-In-Zip Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+  function createZipInZipData() {
+    var Cc = SpecialPowers.wrap(Components).classes;
+    var Ci = SpecialPowers.wrap(Components).interfaces;
+
+    var dirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
+    var testFile = dirSvc.get("ProfD", Ci.nsIFile);
+    testFile.append("fileArchiveReader_42.zip");
+    var outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+    outStream.init(testFile, 0x02 | 0x08 | 0x20, // write, create, truncate
+                   0666, 0);
+
+    var binaryData = "";
+    for (var i = 0, len = binaryString.length / 2; i < len; ++i) {
+      var hex = binaryString[i * 2] + binaryString[i * 2 + 1];
+      binaryData += String.fromCharCode(parseInt(hex,16));
+    }
+    outStream.write(binaryData, binaryData.length);
+    outStream.close();
+
+    var fileList = document.getElementById('fileList');
+    SpecialPowers.wrap(fileList).value = testFile.path;
+
+    return fileList.files[0];
+  }
+
+  function testSteps()
+  {
+    var binaryFile = createZipInZipData();
+
+    // The input is 4 nested zip archives:
+    doLoop(binaryFile, 4);
+
+    yield;
+  }
+
+  function doLoop(blob, loop)
+  {
+    var r = new ArchiveReader(blob);
+    isnot(r, null, "ArchiveReader cannot be null");
+
+    // GetFilename
+    var handle = r.getFilenames();
+    isnot(handle, null, "ArchiveReader.getFilenames() cannot be null");
+    handle.onsuccess = function() {
+      ok(true, "ArchiveReader.getFilenames() should return a 'success'");
+      is(this.result instanceof Array, true, "ArchiveReader.getFilenames() should return an array");
+      is(this.result.length, 1, "ArchiveReader.getFilenames(): the array contains 1 item");
+      ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+
+      dump('Content:\n');
+      for (var i = 0; i < this.result.length; ++i)
+        dump(' * ' + this.result[i] + '\n');
+
+      var handle = r.getFile(this.result[0]);
+      handle.onerror = function() {
+        ok(false, "ArchiveReader.getFile() should not return any 'error'");
+      }
+      handle.onsuccess = function() {
+        ok(true, "ArchiveReader.getFilenames() should return a 'success'");
+        ok(this.reader, r, "ArchiveRequest.reader should be == ArchiveReader");
+
+        if (loop > 0)
+          doLoop(this.result, loop - 1);
+        else
+          doLastLoop(this.result);
+      }
+    }
+    handle.onerror = function() {
+      ok(false, "ArchiveReader.getFilenames() should not return any 'error'");
+    }
+  }
+
+  function doLastLoop(blob)
+  {
+    ok(blob.size == 262144, "The last file size is wrong");
+    ok(blob.name == 'empty.dat', "The last filename is wrong");
+    finishTest();
+  }
+
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();">
+<p id="display">
+  <input id="fileList" type="file"></input>
+</p>
+<script type="text/javascript;version=1.7">
+var binaryString = '' +
+'504B03040A0000000000B0620E415F715F15970300009703000005001C00642E7A69705554090003AC262A50AC262A507578' +
+'0B000104E803000004E8030000504B03040A0000000000B0620E41CFE25F1EF7020000F702000005001C00632E7A69705554' +
+'090003AC262A50AC262A5075780B000104E803000004E8030000504B03040A0000000000B0620E4107D702A4570200005702' +
+'000005001C00622E7A69705554090003AC262A50AC262A5075780B000104E803000004E8030000504B03040A0000000000B0' +
+'620E417E45286DB7010000B701000005001C00612E7A69705554090003AC262A50AC262A5075780B000104E803000004E803' +
+'0000504B0304140000000800F7610E41784909B70F0100000000040009001C00656D7074792E646174555409000351252A50' +
+'57252A5075780B000104E803000004E8030000EDC13101000000C2A0FEA9E76D07A000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' +
+'0000000000000000000000000000000000000000000000000000000000000000000000000000DE00504B01021E0314000000' +
+'0800F7610E41784909B70F01000000000400090018000000000001000000B48100000000656D7074792E6461745554050003' +
+'51252A5075780B000104E803000004E8030000504B050600000000010001004F000000520100000000504B01021E030A0000' +
+'000000B0620E417E45286DB7010000B7010000050018000000000000000000B48100000000612E7A69705554050003AC262A' +
+'5075780B000104E803000004E8030000504B050600000000010001004B000000F60100000000504B01021E030A0000000000' +
+'B0620E4107D702A45702000057020000050018000000000000000000B48100000000622E7A69705554050003AC262A507578' +
+'0B000104E803000004E8030000504B050600000000010001004B000000960200000000504B01021E030A0000000000B0620E' +
+'41CFE25F1EF7020000F7020000050018000000000000000000B48100000000632E7A69705554050003AC262A5075780B0001' +
+'04E803000004E8030000504B050600000000010001004B000000360300000000504B01021E030A0000000000B0620E415F71' +
+'5F159703000097030000050018000000000000000000B48100000000642E7A69705554050003AC262A5075780B000104E803' +
+'000004E8030000504B050600000000010001004B000000D60300000000';
+</script>
+</body>
+
+</html>