auto import from //depot/cupcake/@135843
diff --git a/libzipfile/centraldir.c b/libzipfile/centraldir.c
new file mode 100644
index 0000000..4387ceb
--- /dev/null
+++ b/libzipfile/centraldir.c
@@ -0,0 +1,256 @@
+#include "private.h"

+#include <stdio.h>

+#include <string.h>

+#include <stdlib.h>

+

+enum {

+    // finding the directory

+    CD_SIGNATURE = 0x06054b50,

+    EOCD_LEN     = 22,        // EndOfCentralDir len, excl. comment

+    MAX_COMMENT_LEN = 65535,

+    MAX_EOCD_SEARCH = MAX_COMMENT_LEN + EOCD_LEN,

+

+    // central directory entries

+    ENTRY_SIGNATURE = 0x02014b50,

+    ENTRY_LEN = 46,          // CentralDirEnt len, excl. var fields

+    

+    // local file header

+    LFH_SIZE = 30,

+};

+

+unsigned int

+read_le_int(const unsigned char* buf)

+{

+    return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);

+}

+

+unsigned int

+read_le_short(const unsigned char* buf)

+{

+    return buf[0] | (buf[1] << 8);

+}

+

+static int

+read_central_dir_values(Zipfile* file, const unsigned char* buf, int len)

+{

+    if (len < EOCD_LEN) {

+        // looks like ZIP file got truncated

+        fprintf(stderr, " Zip EOCD: expected >= %d bytes, found %d\n",

+                EOCD_LEN, len);

+        return -1;

+    }

+

+    file->disknum = read_le_short(&buf[0x04]);

+    file->diskWithCentralDir = read_le_short(&buf[0x06]);

+    file->entryCount = read_le_short(&buf[0x08]);

+    file->totalEntryCount = read_le_short(&buf[0x0a]);

+    file->centralDirSize = read_le_int(&buf[0x0c]);

+    file->centralDirOffest = read_le_int(&buf[0x10]);

+    file->commentLen = read_le_short(&buf[0x14]);

+

+    if (file->commentLen > 0) {

+        if (EOCD_LEN + file->commentLen > len) {

+            fprintf(stderr, "EOCD(%d) + comment(%d) exceeds len (%d)\n",

+                    EOCD_LEN, file->commentLen, len);

+            return -1;

+        }

+        file->comment = buf + EOCD_LEN;

+    }

+

+    return 0;

+}

+

+static int

+read_central_directory_entry(Zipfile* file, Zipentry* entry,

+                const unsigned char** buf, ssize_t* len)

+{

+    const unsigned char* p;

+

+    unsigned short  versionMadeBy;

+    unsigned short  versionToExtract;

+    unsigned short  gpBitFlag;

+    unsigned short  compressionMethod;

+    unsigned short  lastModFileTime;

+    unsigned short  lastModFileDate;

+    unsigned long   crc32;

+    unsigned long   compressedSize;

+    unsigned long   uncompressedSize;

+    unsigned short  extraFieldLength;

+    unsigned short  fileCommentLength;

+    unsigned short  diskNumberStart;

+    unsigned short  internalAttrs;

+    unsigned long   externalAttrs;

+    unsigned long   localHeaderRelOffset;

+    const unsigned char*  extraField;

+    const unsigned char*  fileComment;

+    unsigned int dataOffset;

+    unsigned short lfhExtraFieldSize;

+    

+

+    p = *buf;

+

+    if (*len < ENTRY_LEN) {

+        fprintf(stderr, "cde entry not large enough\n");

+        return -1;

+    }

+

+    if (read_le_int(&p[0x00]) != ENTRY_SIGNATURE) {

+        fprintf(stderr, "Whoops: didn't find expected signature\n");

+        return -1;

+    }

+

+    versionMadeBy = read_le_short(&p[0x04]);

+    versionToExtract = read_le_short(&p[0x06]);

+    gpBitFlag = read_le_short(&p[0x08]);

+    entry->compressionMethod = read_le_short(&p[0x0a]);

+    lastModFileTime = read_le_short(&p[0x0c]);

+    lastModFileDate = read_le_short(&p[0x0e]);

+    crc32 = read_le_int(&p[0x10]);

+    compressedSize = read_le_int(&p[0x14]);

+    entry->uncompressedSize = read_le_int(&p[0x18]);

+    entry->fileNameLength = read_le_short(&p[0x1c]);

+    extraFieldLength = read_le_short(&p[0x1e]);

+    fileCommentLength = read_le_short(&p[0x20]);

+    diskNumberStart = read_le_short(&p[0x22]);

+    internalAttrs = read_le_short(&p[0x24]);

+    externalAttrs = read_le_int(&p[0x26]);

+    localHeaderRelOffset = read_le_int(&p[0x2a]);

+

+    p += ENTRY_LEN;

+

+    // filename

+    if (entry->fileNameLength != 0) {

+        entry->fileName = p;

+    } else {

+        entry->fileName = NULL;

+    }

+    p += entry->fileNameLength;

+

+    // extra field

+    if (extraFieldLength != 0) {

+        extraField = p;

+    } else {

+        extraField = NULL;

+    }

+    p += extraFieldLength;

+

+    // comment, if any

+    if (fileCommentLength != 0) {

+        fileComment = p;

+    } else {

+        fileComment = NULL;

+    }

+    p += fileCommentLength;

+    

+    *buf = p;

+

+    // the size of the extraField in the central dir is how much data there is,

+    // but the one in the local file header also contains some padding.

+    p = file->buf + localHeaderRelOffset;

+    extraFieldLength = read_le_short(&p[0x1c]);

+    

+    dataOffset = localHeaderRelOffset + LFH_SIZE

+        + entry->fileNameLength + extraFieldLength;

+    entry->data = file->buf + dataOffset;

+#if 0

+    printf("file->buf=%p entry->data=%p dataOffset=%x localHeaderRelOffset=%d "

+           "entry->fileNameLength=%d extraFieldLength=%d\n",

+           file->buf, entry->data, dataOffset, localHeaderRelOffset,

+           entry->fileNameLength, extraFieldLength);

+#endif

+    return 0;

+}

+

+/*

+ * Find the central directory and read the contents.

+ *

+ * The fun thing about ZIP archives is that they may or may not be

+ * readable from start to end.  In some cases, notably for archives

+ * that were written to stdout, the only length information is in the

+ * central directory at the end of the file.

+ *

+ * Of course, the central directory can be followed by a variable-length

+ * comment field, so we have to scan through it backwards.  The comment

+ * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff

+ * itself, plus apparently sometimes people throw random junk on the end

+ * just for the fun of it.

+ *

+ * This is all a little wobbly.  If the wrong value ends up in the EOCD

+ * area, we're hosed.  This appears to be the way that everbody handles

+ * it though, so we're in pretty good company if this fails.

+ */

+int

+read_central_dir(Zipfile *file)

+{

+    int err;

+

+    const unsigned char* buf = file->buf;

+    ssize_t bufsize = file->bufsize;

+    const unsigned char* eocd;

+    const unsigned char* p;

+    const unsigned char* start;

+    ssize_t len;

+    int i;

+

+    // too small to be a ZIP archive?

+    if (bufsize < EOCD_LEN) {

+        fprintf(stderr, "Length is %d -- too small\n", bufsize);

+        goto bail;

+    }

+

+    // find the end-of-central-dir magic

+    if (bufsize > MAX_EOCD_SEARCH) {

+        start = buf + bufsize - MAX_EOCD_SEARCH;

+    } else {

+        start = buf;

+    }

+    p = buf + bufsize - 4;

+    while (p >= start) {

+        if (*p == 0x50 && read_le_int(p) == CD_SIGNATURE) {

+            eocd = p;

+            break;

+        }

+        p--;

+    }

+    if (p < start) {

+        fprintf(stderr, "EOCD not found, not Zip\n");

+        goto bail;

+    }

+

+    // extract eocd values

+    err = read_central_dir_values(file, eocd, (buf+bufsize)-eocd);

+    if (err != 0) {

+        goto bail;

+    }

+

+    if (file->disknum != 0

+          || file->diskWithCentralDir != 0

+          || file->entryCount != file->totalEntryCount) {

+        fprintf(stderr, "Archive spanning not supported\n");

+        goto bail;

+    }

+

+    // Loop through and read the central dir entries.

+    p = buf + file->centralDirOffest;

+    len = (buf+bufsize)-p;

+    for (i=0; i < file->totalEntryCount; i++) {

+        Zipentry* entry = malloc(sizeof(Zipentry));

+        memset(entry, sizeof(Zipentry), 0);

+

+        err = read_central_directory_entry(file, entry, &p, &len);

+        if (err != 0) {

+            fprintf(stderr, "read_central_directory_entry failed\n");

+            free(entry);

+            goto bail;

+        }

+        

+        // add it to our list

+        entry->next = file->entries;

+        file->entries = entry;

+    }

+

+    return 0;

+bail:

+    return -1;

+}

+