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;
+}
+