mozglue/linker/Zip.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 #include <sys/mman.h>
     6 #include <sys/stat.h>
     7 #include <fcntl.h>
     8 #include <errno.h>
     9 #include <unistd.h>
    10 #include <cstdlib>
    11 #include <algorithm>
    12 #include "Logging.h"
    13 #include "Zip.h"
    15 mozilla::TemporaryRef<Zip>
    16 Zip::Create(const char *filename)
    17 {
    18   /* Open and map the file in memory */
    19   AutoCloseFD fd(open(filename, O_RDONLY));
    20   if (fd == -1) {
    21     LOG("Error opening %s: %s", filename, strerror(errno));
    22     return nullptr;
    23   }
    24   struct stat st;
    25   if (fstat(fd, &st) == -1) {
    26     LOG("Error stating %s: %s", filename, strerror(errno));
    27     return nullptr;
    28   }
    29   size_t size = st.st_size;
    30   if (size <= sizeof(CentralDirectoryEnd)) {
    31     LOG("Error reading %s: too short", filename);
    32     return nullptr;
    33   }
    34   void *mapped = mmap(nullptr, size, PROT_READ, MAP_SHARED, fd, 0);
    35   if (mapped == MAP_FAILED) {
    36     LOG("Error mmapping %s: %s", filename, strerror(errno));
    37     return nullptr;
    38   }
    39   DEBUG_LOG("Mapped %s @%p", filename, mapped);
    41   return Create(filename, mapped, size);
    42 }
    44 mozilla::TemporaryRef<Zip>
    45 Zip::Create(const char *filename, void *mapped, size_t size)
    46 {
    47   mozilla::RefPtr<Zip> zip = new Zip(filename, mapped, size);
    49   // If neither the first Local File entry nor central directory entries
    50   // have been found, the zip was invalid.
    51   if (!zip->nextFile && !zip->entries) {
    52     LOG("%s - Invalid zip", filename);
    53     return nullptr;
    54   }
    56   ZipCollection::Singleton.Register(zip);
    57   return zip;
    58 }
    60 Zip::Zip(const char *filename, void *mapped, size_t size)
    61 : name(filename ? strdup(filename) : nullptr)
    62 , mapped(mapped)
    63 , size(size)
    64 , nextFile(LocalFile::validate(mapped)) // first Local File entry
    65 , nextDir(nullptr)
    66 , entries(nullptr)
    67 {
    68   // If the first local file entry couldn't be found (which can happen
    69   // with optimized jars), check the first central directory entry.
    70   if (!nextFile)
    71     GetFirstEntry();
    72 }
    74 Zip::~Zip()
    75 {
    76   ZipCollection::Forget(this);
    77   if (name) {
    78     munmap(mapped, size);
    79     DEBUG_LOG("Unmapped %s @%p", name, mapped);
    80     free(name);
    81   }
    82 }
    84 bool
    85 Zip::GetStream(const char *path, Zip::Stream *out) const
    86 {
    87   DEBUG_LOG("%s - GetFile %s", name, path);
    88   /* Fast path: if the Local File header on store matches, we can return the
    89    * corresponding stream right away.
    90    * However, the Local File header may not contain enough information, in
    91    * which case the 3rd bit on the generalFlag is set. Unfortunately, this
    92    * bit is also set in some archives even when we do have the data (most
    93    * notably the android packages as built by the Mozilla build system).
    94    * So instead of testing the generalFlag bit, only use the fast path when
    95    * we haven't read the central directory entries yet, and when the
    96    * compressed size as defined in the header is not filled (which is a
    97    * normal condition for the bit to be set). */
    98   if (nextFile && nextFile->GetName().Equals(path) &&
    99       !entries && (nextFile->compressedSize != 0)) {
   100     DEBUG_LOG("%s - %s was next file: fast path", name, path);
   101     /* Fill Stream info from Local File header content */
   102     const char *data = reinterpret_cast<const char *>(nextFile->GetData());
   103     out->compressedBuf = data;
   104     out->compressedSize = nextFile->compressedSize;
   105     out->uncompressedSize = nextFile->uncompressedSize;
   106     out->type = static_cast<Stream::Type>(uint16_t(nextFile->compression));
   108     /* Find the next Local File header. It is usually simply following the
   109      * compressed stream, but in cases where the 3rd bit of the generalFlag
   110      * is set, there is a Data Descriptor header before. */
   111     data += nextFile->compressedSize;
   112     if ((nextFile->generalFlag & 0x8) && DataDescriptor::validate(data)) {
   113       data += sizeof(DataDescriptor);
   114     }
   115     nextFile = LocalFile::validate(data);
   116     return true;
   117   }
   119   /* If the directory entry we have in store doesn't match, scan the Central
   120    * Directory for the entry corresponding to the given path */
   121   if (!nextDir || !nextDir->GetName().Equals(path)) {
   122     const DirectoryEntry *entry = GetFirstEntry();
   123     DEBUG_LOG("%s - Scan directory entries in search for %s", name, path);
   124     while (entry && !entry->GetName().Equals(path)) {
   125       entry = entry->GetNext();
   126     }
   127     nextDir = entry;
   128   }
   129   if (!nextDir) {
   130     DEBUG_LOG("%s - Couldn't find %s", name, path);
   131     return false;
   132   }
   134   /* Find the Local File header corresponding to the Directory entry that
   135    * was found. */
   136   nextFile = LocalFile::validate(static_cast<const char *>(mapped)
   137                              + nextDir->offset);
   138   if (!nextFile) {
   139     LOG("%s - Couldn't find the Local File header for %s", name, path);
   140     return false;
   141   }
   143   /* Fill Stream info from Directory entry content */
   144   const char *data = reinterpret_cast<const char *>(nextFile->GetData());
   145   out->compressedBuf = data;
   146   out->compressedSize = nextDir->compressedSize;
   147   out->uncompressedSize = nextDir->uncompressedSize;
   148   out->type = static_cast<Stream::Type>(uint16_t(nextDir->compression));
   150   /* Store the next directory entry */
   151   nextDir = nextDir->GetNext();
   152   nextFile = nullptr;
   153   return true;
   154 }
   156 const Zip::DirectoryEntry *
   157 Zip::GetFirstEntry() const
   158 {
   159   if (entries)
   160     return entries;
   162   const CentralDirectoryEnd *end = nullptr;
   163   const char *_end = static_cast<const char *>(mapped) + size
   164                      - sizeof(CentralDirectoryEnd);
   166   /* Scan for the Central Directory End */
   167   for (; _end > mapped && !end; _end--)
   168     end = CentralDirectoryEnd::validate(_end);
   169   if (!end) {
   170     LOG("%s - Couldn't find end of central directory record", name);
   171     return nullptr;
   172   }
   174   entries = DirectoryEntry::validate(static_cast<const char *>(mapped)
   175                                  + end->offset);
   176   if (!entries) {
   177     LOG("%s - Couldn't find central directory record", name);
   178   }
   179   return entries;
   180 }
   182 ZipCollection ZipCollection::Singleton;
   184 mozilla::TemporaryRef<Zip>
   185 ZipCollection::GetZip(const char *path)
   186 {
   187   /* Search the list of Zips we already have for a match */
   188   for (std::vector<Zip *>::iterator it = Singleton.zips.begin();
   189        it < Singleton.zips.end(); ++it) {
   190     if ((*it)->GetName() && (strcmp((*it)->GetName(), path) == 0))
   191       return *it;
   192   }
   193   return Zip::Create(path);
   194 }
   196 void
   197 ZipCollection::Register(Zip *zip)
   198 {
   199   Singleton.zips.push_back(zip);
   200 }
   202 void
   203 ZipCollection::Forget(Zip *zip)
   204 {
   205   DEBUG_LOG("ZipCollection::Forget(\"%s\")", zip->GetName());
   206   std::vector<Zip *>::iterator it = std::find(Singleton.zips.begin(),
   207                                               Singleton.zips.end(), zip);
   208   if (*it == zip) {
   209     Singleton.zips.erase(it);
   210   } else {
   211     DEBUG_LOG("ZipCollection::Forget: didn't find \"%s\" in bookkeeping", zip->GetName());
   212   }
   213 }

mercurial