Switch to using hand-rolled ZIP container "library" for EPUB files.

pull/193/head
Michael Sweet 7 years ago
parent 7f1beff016
commit 7c2551e996
  1. 24
      Makefile.in
  2. 133
      configure
  3. 7
      configure.ac
  4. 2
      doc/mxml.man
  5. 109
      mxmldoc.c
  6. 14
      xcode/mxml.xcodeproj/project.pbxproj
  7. 809
      zipc.c
  8. 71
      zipc.h

@ -82,7 +82,7 @@ DOCFILES = doc/mxml.html doc/mxmldoc.xsd README.md COPYING CHANGES.md
PUBLIBOBJS = mxml-attr.o mxml-entity.o mxml-file.o mxml-get.o \
mxml-index.o mxml-node.o mxml-search.o mxml-set.o
LIBOBJS = $(PUBLIBOBJS) mxml-private.o mxml-string.o
OBJS = mxmldoc.o testmxml.o $(LIBOBJS)
OBJS = mxmldoc.o testmxml.o zipc.o $(LIBOBJS)
ALLTARGETS = $(LIBMXML) mxmldoc testmxml mxml.xml
CROSSTARGETS = $(LIBMXML) mxmldoc
TARGETS = $(@TARGETS@)
@ -293,15 +293,16 @@ libmxml.1.dylib: $(LIBOBJS)
# mxmldoc
#
mxmldoc: $(LIBMXML) mxmldoc.o
mxmldoc: $(LIBMXML) mxmldoc.o zipc.o
echo Linking $@...
$(CC) -L. $(LDFLAGS) -o $@ mxmldoc.o -lmxml $(LIBS)
$(CC) -L. $(LDFLAGS) -o $@ mxmldoc.o zipc.o -lmxml $(LIBS)
mxmldoc-static: libmxml.a mxmldoc.o
echo Linking $@...
$(CC) $(LDFLAGS) -o $@ mxmldoc.o libmxml.a $(LIBS)
$(CC) $(LDFLAGS) -o $@ mxmldoc.o zipc.o libmxml.a $(LIBS)
mxmldoc.o: mxml.h
mxmldoc.o: mxml.h zipc.h
zipc.o: zipc.h
#
@ -347,16 +348,23 @@ testmxml.o: mxml.h
mxml.xml: mxmldoc-static mxml.h $(PUBLIBOBJS:.o=.c)
echo Generating API documentation...
$(RM) mxml.xml
./mxmldoc-static --header doc/reference.heading mxml.xml mxml.h $(PUBLIBOBJS:.o=.c) >doc/reference.html
./mxmldoc-static --header doc/reference.heading \
--docversion @VERSION@ --author "Michael R Sweet" \
--copyright "Copyright 2003-2017, All Rights Reserved." \
mxml.xml mxml.h $(PUBLIBOBJS:.o=.c) >doc/reference.html
./mxmldoc-static --man mxml --title "Mini-XML API" \
--intro doc/intro.man --footer doc/footer.man \
--author "Michael R Sweet" \
--copyright "Copyright 2003-2017, All Rights Reserved." \
mxml.xml >doc/mxml.man
if test "x`uname`" = xDarwin; then \
./mxmldoc-static --docset org.minixml.docset \
--docversion @VERSION@ --feedname minixml.org \
--feedurl http://www.minixml.org/org.minixml.atom \
--header doc/docset.header --intro doc/docset.intro \
--css doc/docset.css --title "Mini-XML API Reference" \
--css doc/docset.css --author "Michael R Sweet" \
--copyright "Copyright 2003-2017, All Rights Reserved." \
--title "Mini-XML API Reference" \
mxml.xml || exit 1; \
$(RM) org.minixml.atom; \
xcrun docsetutil package --output org.minixml.xar \
@ -374,6 +382,8 @@ mxml.epub: mxml.xml
echo Generating EPUB API documentation...
./mxmldoc-static --header doc/docset.header \
--intro doc/docset.intro --css doc/docset.css \
--docversion @VERSION@ --author "Michael R Sweet" \
--copyright "Copyright 2003-2017, All Rights Reserved." \
--title "Mini-XML API Reference" --epub mxml.epub mxml.xml

133
configure vendored

@ -3963,7 +3963,75 @@ if test $ac_cv_c_long_long = yes; then
fi
ac_ext=c
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for library containing gzgets" >&5
$as_echo_n "checking for library containing gzgets... " >&6; }
if ${ac_cv_search_gzgets+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_func_search_save_LIBS=$LIBS
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char gzgets ();
int
main ()
{
return gzgets ();
;
return 0;
}
_ACEOF
for ac_lib in '' z; do
if test -z "$ac_lib"; then
ac_res="none required"
else
ac_res=-l$ac_lib
LIBS="-l$ac_lib $ac_func_search_save_LIBS"
fi
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_search_gzgets=$ac_res
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext
if ${ac_cv_search_gzgets+:} false; then :
break
fi
done
if ${ac_cv_search_gzgets+:} false; then :
else
ac_cv_search_gzgets=no
fi
rm conftest.$ac_ext
LIBS=$ac_func_search_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_search_gzgets" >&5
$as_echo "$ac_cv_search_gzgets" >&6; }
ac_res=$ac_cv_search_gzgets
if test "$ac_res" != no; then :
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
LIBS="-lz $LIBS"
fi
# Check whether --enable-threads was given.
if test "${enable_threads+set}" = set; then :
enableval=$enable_threads;
fi
have_pthread=no
PTHREAD_FLAGS=""
PTHREAD_LIBS=""
if test "x$enable_threads" != xno; then
ac_ext=c
ac_cpp='$CPP $CPPFLAGS'
ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5'
ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5'
@ -4360,68 +4428,7 @@ fi
done
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for archive_write_new in -larchive" >&5
$as_echo_n "checking for archive_write_new in -larchive... " >&6; }
if ${ac_cv_lib_archive_archive_write_new+:} false; then :
$as_echo_n "(cached) " >&6
else
ac_check_lib_save_LIBS=$LIBS
LIBS="-larchive $LIBS"
cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char archive_write_new ();
int
main ()
{
return archive_write_new ();
;
return 0;
}
_ACEOF
if ac_fn_c_try_link "$LINENO"; then :
ac_cv_lib_archive_archive_write_new=yes
else
ac_cv_lib_archive_archive_write_new=no
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext conftest.$ac_ext
LIBS=$ac_check_lib_save_LIBS
fi
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_archive_archive_write_new" >&5
$as_echo "$ac_cv_lib_archive_archive_write_new" >&6; }
if test "x$ac_cv_lib_archive_archive_write_new" = xyes; then :
LIBS="-larchive $LIBS"
ac_fn_c_check_header_mongrel "$LINENO" "archive.h" "ac_cv_header_archive_h" "$ac_includes_default"
if test "x$ac_cv_header_archive_h" = xyes; then :
$as_echo "#define HAVE_ARCHIVE_H 1" >>confdefs.h
fi
fi
# Check whether --enable-threads was given.
if test "${enable_threads+set}" = set; then :
enableval=$enable_threads;
fi
have_pthread=no
PTHREAD_FLAGS=""
PTHREAD_LIBS=""
if test "x$enable_threads" != xno; then
ac_fn_c_check_header_mongrel "$LINENO" "pthread.h" "ac_cv_header_pthread_h" "$ac_includes_default"
ac_fn_c_check_header_mongrel "$LINENO" "pthread.h" "ac_cv_header_pthread_h" "$ac_includes_default"
if test "x$ac_cv_header_pthread_h" = xyes; then :
$as_echo "#define HAVE_PTHREAD_H 1" >>confdefs.h

@ -116,11 +116,8 @@ if test $ac_cv_c_long_long = yes; then
AC_DEFINE(HAVE_LONG_LONG)
fi
dnl EPUB support (via libarchive support for writing ZIP files...)
AC_CHECK_LIB(archive,archive_write_new,[
LIBS="-larchive $LIBS"
AC_CHECK_HEADER(archive.h, AC_DEFINE(HAVE_ARCHIVE_H))
])
dnl EPUB support (via libz and zipc)
AC_SEARCH_LIBS(gzgets,z,[LIBS="-lz $LIBS"])
dnl Threading support
AC_ARG_ENABLE(threads, [ --enable-threads enable multi-threading support])

@ -1,4 +1,4 @@
.TH mxml 3 "Mini-XML API" "04/01/17" "Mini-XML API"
.TH mxml 3 "Mini-XML API" "04/04/17" "Mini-XML API"
.SH NAME
mxml \- Mini-XML API
.SH INCLUDE FILE

@ -20,6 +20,7 @@
#include "config.h"
#include "mxml.h"
#include "zipc.h"
#include <time.h>
#include <sys/stat.h>
#ifndef WIN32
@ -32,14 +33,6 @@
extern char **environ;
#endif /* __APPLE__ */
#ifdef HAVE_ARCHIVE_H
# include <archive.h>
# include <archive_entry.h>
#elif defined(__APPLE__) /* Use archive 2.8.3 headers for macOS */
# include "xcode/archive.h"
# include "xcode/archive_entry.h"
#endif /* HAVE_ARCHIVE_H */
/*
* This program scans source and header files and produces public API
@ -608,12 +601,7 @@ main(int argc, /* I - Number of command-line args */
* Write EPUB (XHTML) documentation...
*/
#if defined(HAVE_ARCHIVE_H) || defined(__APPLE__)
write_epub(section, title ? title : "Documentation", author ? author : "Unknown", copyright ? copyright : "Unknown", docversion ? docversion : "0.0", footerfile, headerfile, introfile, cssfile, epubfile, mxmldoc);
#else
fputs("mxmldoc: EPUB support not compiled in.\n", stderr);
return (1);
#endif /* HAVE_ARCHIVE_H || __APPLE__ */
break;
case OUTPUT_HTML :
@ -1089,6 +1077,9 @@ epub_ws_cb(mxml_node_t *node, /* I - Element node */
switch (where)
{
case MXML_WS_BEFORE_CLOSE :
if (node->child && node->child->type != MXML_ELEMENT)
return (NULL);
for (depth = -4; node; node = node->parent, depth += 2);
if (depth > 40)
return (spaces);
@ -1111,6 +1102,9 @@ epub_ws_cb(mxml_node_t *node, /* I - Element node */
default :
case MXML_WS_AFTER_OPEN :
if (node->child && node->child->type != MXML_ELEMENT)
return (NULL);
return ("\n");
}
}
@ -3396,7 +3390,6 @@ write_element(FILE *out, /* I - Output file */
}
#if defined(HAVE_ARCHIVE_H) || defined(__APPLE__)
/*
* 'write_epub()' - Write documentation as an EPUB file.
*/
@ -3425,11 +3418,10 @@ write_epub(const char *section, /* I - Section */
*defval; /* Default value */
char epubbase[256], /* Base name of EPUB file (identifier) */
*epubptr; /* Pointer into base name */
struct archive *epub; /* EPUB archive */
struct archive_entry *entry; /* EPUB entry */
zipc_t *epub; /* EPUB ZIP container */
zipc_file_t *epubf; /* File in EPUB ZIP container */
char xhtmlfile[1024], /* XHTML output filename */
*xhtmlptr; /* Pointer into output filename */
struct stat xhtmlinfo; /* XHTML output file information */
mxml_node_t *content_opf, /* content.opf file */
*package, /* package node */
*metadata, /* metadata node */
@ -3451,7 +3443,6 @@ write_epub(const char *section, /* I - Section */
*navLabel; /* navLabel node */
char *toc_ncx_string; /* toc.ncx file as a string */
char toc_xhtmlfile[1024]; /* XHTML file for table-of-contents */
struct stat toc_xhtmlinfo; /* XHTML file info */
int toc_level; /* Current table-of-contents level */
static const char *mimetype = /* mimetype file as a string */
"application/epub+zip";
@ -3956,110 +3947,52 @@ write_epub(const char *section, /* I - Section */
* Make the EPUB archive...
*/
epub = archive_write_new();
archive_write_set_format_zip(epub);
archive_write_set_options(epub, "!experimental,!zip64");
archive_write_open_filename(epub, epubfile);
epub = zipcOpen(epubfile, "w");
/* mimetype file */
entry = archive_entry_new();
archive_entry_set_pathname(entry, "mimetype");
archive_entry_set_size(entry, strlen(mimetype));
archive_entry_set_filetype(entry, AE_IFREG);
archive_write_header(epub, entry);
archive_write_data(epub, mimetype, strlen(mimetype));
archive_entry_free(entry);
/* META-INF directory */
entry = archive_entry_new();
archive_entry_set_pathname(entry, "META-INF");
archive_entry_set_size(entry, 0);
archive_entry_set_filetype(entry, AE_IFDIR);
archive_write_header(epub, entry);
archive_entry_free(entry);
zipcCreateFileWithString(epub, "mimetype", mimetype);
/* META-INF/container.xml file */
entry = archive_entry_new();
archive_entry_set_pathname(entry, "META-INF/container.xml");
archive_entry_set_size(entry, strlen(container_xml));
archive_entry_set_filetype(entry, AE_IFREG);
archive_write_header(epub, entry);
archive_write_data(epub, container_xml, strlen(container_xml));
archive_entry_free(entry);
/* OEBPS directory */
entry = archive_entry_new();
archive_entry_set_pathname(entry, "OEBPS");
archive_entry_set_size(entry, 0);
archive_entry_set_filetype(entry, AE_IFDIR);
archive_write_header(epub, entry);
archive_entry_free(entry);
zipcCreateFileWithString(epub, "META-INF/container.xml", container_xml);
/* OEBPS/body.xhtml file */
stat(xhtmlfile, &xhtmlinfo);
entry = archive_entry_new();
archive_entry_set_pathname(entry, "OEBPS/body.xhtml");
archive_entry_set_size(entry, xhtmlinfo.st_size);
archive_entry_set_filetype(entry, AE_IFREG);
archive_write_header(epub, entry);
epubf = zipcCreateFile(epub, "OEBPS/body.xhtml", 1);
if ((fp = fopen(xhtmlfile, "r")) != NULL)
{
char buffer[65536]; /* Copy buffer */
int length; /* Number of bytes */
while ((length = (int)fread(buffer, 1, sizeof(buffer), fp)) > 0)
archive_write_data(epub, buffer, length);
zipcFileWrite(epubf, buffer, length);
fclose(fp);
zipcFileFinish(epubf);
}
unlink(xhtmlfile);
archive_entry_free(entry);
/* OEBPS/content.opf file */
entry = archive_entry_new();
archive_entry_set_pathname(entry, "OEBPS/content.opf");
archive_entry_set_size(entry, strlen(content_opf_string));
archive_entry_set_filetype(entry, AE_IFREG);
archive_write_header(epub, entry);
archive_write_data(epub, content_opf_string, strlen(content_opf_string));
archive_entry_free(entry);
zipcCreateFileWithString(epub, "OEBPS/content.opf", content_opf_string);
/* OEBPS/toc.ncx file */
entry = archive_entry_new();
archive_entry_set_pathname(entry, "OEBPS/toc.ncx");
archive_entry_set_size(entry, strlen(toc_ncx_string));
archive_entry_set_filetype(entry, AE_IFREG);
archive_write_header(epub, entry);
archive_write_data(epub, toc_ncx_string, strlen(toc_ncx_string));
archive_entry_free(entry);
zipcCreateFileWithString(epub, "OEBPS/toc.ncx", toc_ncx_string);
/* OEBPS/toc.xhtml file */
stat(toc_xhtmlfile, &toc_xhtmlinfo);
entry = archive_entry_new();
archive_entry_set_pathname(entry, "OEBPS/toc.xhtml");
archive_entry_set_size(entry, toc_xhtmlinfo.st_size);
archive_entry_set_filetype(entry, AE_IFREG);
archive_write_header(epub, entry);
epubf = zipcCreateFile(epub, "OEBPS/toc.xhtml", 1);
if ((fp = fopen(toc_xhtmlfile, "r")) != NULL)
{
char buffer[65536]; /* Copy buffer */
int length; /* Number of bytes */
while ((length = (int)fread(buffer, 1, sizeof(buffer), fp)) > 0)
archive_write_data(epub, buffer, length);
zipcFileWrite(epubf, buffer, length);
fclose(fp);
zipcFileFinish(epubf);
}
unlink(toc_xhtmlfile);
archive_entry_free(entry);
archive_write_close(epub);
archive_write_finish(epub);
zipcClose(epub);
}
#endif /* HAVE_ARCHIVE_H || __APPLE__ */
/*

@ -40,7 +40,8 @@
272C00431E8C6B34007EBCAC /* mxmldoc.c in Sources */ = {isa = PBXBuildFile; fileRef = 272C00331E8C6ADE007EBCAC /* mxmldoc.c */; };
272C00501E8C6B89007EBCAC /* libmxml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 272C00051E8C6664007EBCAC /* libmxml.a */; };
272C00511E8C6B8E007EBCAC /* libmxml.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 272C00051E8C6664007EBCAC /* libmxml.a */; };
272C00561E8EF972007EBCAC /* libarchive.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 272C00551E8EF972007EBCAC /* libarchive.tbd */; };
272C00591E943266007EBCAC /* zipc.c in Sources */ = {isa = PBXBuildFile; fileRef = 272C00571E943266007EBCAC /* zipc.c */; };
272C005B1E943423007EBCAC /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 272C005A1E943423007EBCAC /* libz.tbd */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -122,6 +123,9 @@
272C00391E8C6AEB007EBCAC /* testmxml */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = testmxml; sourceTree = BUILT_PRODUCTS_DIR; };
272C00401E8C6B1B007EBCAC /* testmxml.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = testmxml.c; path = ../testmxml.c; sourceTree = "<group>"; };
272C00551E8EF972007EBCAC /* libarchive.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libarchive.tbd; path = usr/lib/libarchive.tbd; sourceTree = SDKROOT; };
272C00571E943266007EBCAC /* zipc.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = zipc.c; path = ../zipc.c; sourceTree = "<group>"; };
272C00581E943266007EBCAC /* zipc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zipc.h; path = ../zipc.h; sourceTree = "<group>"; };
272C005A1E943423007EBCAC /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -136,7 +140,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
272C00561E8EF972007EBCAC /* libarchive.tbd in Frameworks */,
272C005B1E943423007EBCAC /* libz.tbd in Frameworks */,
272C00511E8C6B8E007EBCAC /* libmxml.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -185,8 +189,10 @@
272C00321E8C6ABB007EBCAC /* tools */ = {
isa = PBXGroup;
children = (
272C00401E8C6B1B007EBCAC /* testmxml.c */,
272C00331E8C6ADE007EBCAC /* mxmldoc.c */,
272C00401E8C6B1B007EBCAC /* testmxml.c */,
272C00571E943266007EBCAC /* zipc.c */,
272C00581E943266007EBCAC /* zipc.h */,
);
name = tools;
sourceTree = "<group>";
@ -194,6 +200,7 @@
272C00541E8EF971007EBCAC /* Frameworks */ = {
isa = PBXGroup;
children = (
272C005A1E943423007EBCAC /* libz.tbd */,
272C00551E8EF972007EBCAC /* libarchive.tbd */,
);
name = Frameworks;
@ -352,6 +359,7 @@
buildActionMask = 2147483647;
files = (
272C00431E8C6B34007EBCAC /* mxmldoc.c in Sources */,
272C00591E943266007EBCAC /* zipc.c in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

809
zipc.c

@ -0,0 +1,809 @@
/*
* Implementation of ZIP container mini-library.
*
* https://github.com/michaelrsweet/zipc
*
* Copyright 2017 by Michael R Sweet.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* Include necessary headers...
*/
#include "zipc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <zlib.h>
/*
* Local constants...
*/
#define ZIPC_LOCAL_HEADER 0x04034b50 /* Start of a local file header */
#define ZIPC_DIR_HEADER 0x02014b50 /* File header in central directory */
#define ZIPC_DIGITAL_SIG 0x05054b50 /* Digital signature at end */
#define ZIPC_END_RECORD 0x06054b50 /* End of central directory record */
#define ZIPC_MADE_BY 0x0314 /* Version made by UNIX using zip 2.0 */
#define ZIPC_VERSION 0x0014 /* Version needed: 2.0 */
#define ZIPC_FLAG_NONE 0 /* No flags */
#define ZIPC_FLAG_CMASK 0x0006 /* Compression fields */
#define ZIPC_FLAG_CNORMAL 0x0000 /* Normal compression */
#define ZIPC_FLAG_CMAX 0x0002 /* Maximum compression */
#define ZIPC_FLAG_CFAST 0x0004 /* Fast compression */
#define ZIPC_FLAG_CSUPER 0x0006 /* Super fast compression */
#define ZIPC_FLAG_DATA 0x0008 /* Length and CRC-32 fields follow data */
#define ZIPC_COMP_STORE 0 /* No compression */
#define ZIPC_COMP_DEFLATE 8 /* Deflate compression */
/*
* Local types...
*/
struct _zipc_s
{
FILE *fp; /* ZIP file */
const char *error; /* Last error message */
int alloc_files, /* Allocated file entries in ZIP */
num_files; /* Number of file entries in ZIP */
zipc_file_t *files; /* File entries in ZIP */
unsigned int modtime; /* MS-DOS modification date/time */
z_stream stream; /* Deflate stream for current file */
char buffer[16384]; /* Deflate output buffer */
};
struct _zipc_file_s
{
zipc_t *zc; /* ZIP container */
char filename[256]; /* File/directory name */
unsigned short flags; /* General purpose bit flags */
unsigned short method; /* Compression method */
unsigned int crc32; /* 32-bit CRC */
size_t compressed_size; /* Size of file (compressed) */
size_t uncompressed_size; /* Size of file (uncompressed) */
size_t offset; /* Offset of this entry in file */
};
/*
* Local functions...
*/
static zipc_file_t *zipc_add_file(zipc_t *zc, const char *filename, int compression);
static int zipc_write(zipc_t *zc, const void *buffer, size_t bytes);
static int zipc_write_dir_header(zipc_t *zc, zipc_file_t *zf);
static int zipc_write_local_header(zipc_t *zc, zipc_file_t *zf);
static int zipc_write_local_trailer(zipc_t *zc, zipc_file_t *zf);
static int zipc_write_u16(zipc_t *zc, unsigned value);
static int zipc_write_u32(zipc_t *zc, unsigned value);
/*
* 'zipcClose()' - Close a ZIP container, writing out the central directory.
*/
int /* O - 0 on success, -1 on error */
zipcClose(zipc_t *zc) /* I - ZIP container */
{
size_t i; /* Looping var */
zipc_file_t *zf; /* Current file */
long start, end; /* Central directory offsets */
int status = 0; /* Return status */
/*
* First write the central directory headers...
*/
start = ftell(zc->fp);
for (i = zc->num_files, zf = zc->files; i > 0; i --, zf ++)
status |= zipc_write_dir_header(zc, zf);
end = ftell(zc->fp);
/*
* Then write the end of central directory block...
*/
status |= zipc_write_u32(zc, ZIPC_END_RECORD);
status |= zipc_write_u16(zc, 0); /* Disk number */
status |= zipc_write_u16(zc, 0); /* Disk number containing directory */
status |= zipc_write_u16(zc, zc->num_files);
status |= zipc_write_u16(zc, zc->num_files);
status |= zipc_write_u32(zc, end - start);
status |= zipc_write_u32(zc, (unsigned)start);
status |= zipc_write_u16(zc, 0); /* file comment length */
if (fclose(zc->fp))
status = -1;
if (zc->alloc_files)
free(zc->files);
free(zc);
return (status);
}
/*
* 'zipcCreateFile()' - Create a ZIP container file.
*
* The "filename" value is the path within the ZIP container. Directories are
* separated by the forward slash ("/").
*
* The "compressed" value determines whether the file is compressed within the
* container.
*/
zipc_file_t * /* I - ZIP container file */
zipcCreateFile(
zipc_t *zc, /* I - ZIP container */
const char *filename, /* I - Filename in container */
int compressed) /* I - 0 for uncompressed, 1 for compressed */
{
/*
* Add the file and write the header...
*/
zipc_file_t *zf = zipc_add_file(zc, filename, compressed);
/* ZIP container file */
if (zipc_write_local_header(zc, zf))
return (NULL);
else
return (zf);
}
/*
* 'zipcCreateFileWithString()' - Add a file whose contents are a string.
*
* This function should be used for short ZIP container files like mimetype
* or container descriptions.
*
* Note: Files added this way are not compressed.
*/
int /* O - 0 on success, -1 on failure */
zipcCreateFileWithString(
zipc_t *zc, /* I - ZIP container */
const char *filename, /* I - Filename in container */
const char *contents) /* I - Contents of file */
{
zipc_file_t *zf = zipc_add_file(zc, filename, 0);
/* ZIP container file */
size_t len = strlen(contents); /* Length of contents */
int status = 0; /* Return status */
if (zf)
{
zf->uncompressed_size = len;
zf->compressed_size = len;
zf->crc32 = crc32(zf->crc32, (const Bytef *)contents, len);
status |= zipc_write_local_header(zc, zf);
status |= zipc_write(zc, contents, len);
}
else
status = -1;
return (status);
}
/*
* 'zipcError()' - Return a message describing the last detected error.
*/
const char * /* O - Error string or NULL */
zipcError(zipc_t *zc) /* I - ZIP container */
{
return (zc ? zc->error : NULL);
}
/*
* 'zipcFileFinish()' - Finish writing to a file in a ZIP container.
*/
int /* O - 0 on success, -1 on error */
zipcFileFinish(zipc_file_t *zf) /* I - ZIP container file */
{
int status = 0; /* Return status */
zipc_t *zc = zf->zc; /* ZIP container */
if (zf->method != ZIPC_COMP_STORE)
{
int zstatus; /* Deflate status */
while ((zstatus = deflate(&zc->stream, Z_FINISH)) != Z_STREAM_END)
{
if (zstatus < Z_OK && zstatus != Z_BUF_ERROR)
{
zc->error = "Deflate failed.";
status = -1;
break;
}
status |= zipc_write(zf->zc, zc->buffer, (char *)zc->stream.next_out - zc->buffer);
zf->compressed_size += (char *)zc->stream.next_out - zc->buffer;
zc->stream.next_out = (Bytef *)zc->buffer;
zc->stream.avail_out = sizeof(zc->buffer);
}
if ((char *)zc->stream.next_out > zc->buffer)
{
status |= zipc_write(zf->zc, zc->buffer, (char *)zc->stream.next_out - zc->buffer);
zf->compressed_size += (char *)zc->stream.next_out - zc->buffer;
}
deflateEnd(&zc->stream);
}
status |= zipc_write_local_trailer(zc, zf);
return (status);
}
/*
* 'zipcFilePrintf()' - Write a formatted string to a file.
*
* The "zf" value is the one returned by the @link zipc_start_file@ function
* used to create the ZIP container file.
*
* The "format" value is a standard printf format string and is followed by
* any arguments needed by the string.
*/
int /* O - 0 on success, -1 on error */
zipcFilePrintf(zipc_file_t *zf, /* I - ZIP container file */
const char *format, /* I - Printf-style format string */
...) /* I - Additional arguments as needed */
{
char buffer[8192]; /* Format buffer */
va_list ap; /* Pointer to additional arguments */
va_start(ap, format);
if (vsnprintf(buffer, sizeof(buffer), format, ap) < 0)
{
zf->zc->error = strerror(errno);
va_end(ap);
return (-1);
}
va_end(ap);
return (zipcFileWrite(zf, buffer, strlen(buffer)));
}
/*
* 'zipcFilePuts()' - Write a string to a file.
*
* The "zf" value is the one returned by the @link zipc_start_file@ function
* used to create the ZIP container file.
*
* The "s" value is literal string that is written to the file. No newline is
* added.
*/
int /* O - 0 on success, -1 on error */
zipcFilePuts(zipc_file_t *zf, /* I - ZIP container file */
const char *s) /* I - String to write */
{
return (zipcFileWrite(zf, s, strlen(s)));
}
/*
* 'zipcFileWrite()' - Write data to a ZIP container file.
*
* The "zf" value is the one returned by the @link zipc_file_start@ function
* used to create the ZIP container file.
*
* The "data" value points to the bytes to be written.
*
* The "bytes" value specifies the number of bytes to write.
*/
int /* O - 0 on success, -1 on error */
zipcFileWrite(zipc_file_t *zf, /* I - ZIP container file */
const void *data, /* I - Data to write */
size_t bytes) /* I - Number of bytes to write */
{
int status = 0; /* Return status */
zipc_t *zc = zf->zc; /* ZIP container */
zf->uncompressed_size += bytes;
zf->crc32 = crc32(zf->crc32, (const Bytef *)data, bytes);
if (zf->method == ZIPC_COMP_STORE)
{
/*
* Store the contents as literal data...
*/
status = zipc_write(zc, data, bytes);
zf->compressed_size += bytes;
}
else
{
/*
* Deflate (compress) the contents...
*/
int zstatus; /* Deflate status */
zc->stream.next_in = (Bytef *)data;
zc->stream.avail_in = (unsigned)bytes;
while (zc->stream.avail_in > 0)
{
if (zc->stream.avail_out < (int)(sizeof(zc->buffer) / 8))
{
status |= zipc_write(zf->zc, zc->buffer, (char *)zc->stream.next_out - zc->buffer);
zf->compressed_size += (char *)zc->stream.next_out - zc->buffer;
zc->stream.next_out = (Bytef *)zc->buffer;
zc->stream.avail_out = sizeof(zc->buffer);
}
zstatus = deflate(&zc->stream, Z_NO_FLUSH);
if (zstatus < Z_OK && zstatus != Z_BUF_ERROR)
{
zc->error = "Deflate failed.";
status = -1;
break;
}
}
}
return (status);
}
/*
* 'zipcFileXMLPrintf()' - Write a formatted XML string to a file.
*
* The "zf" value is the one returned by the @link zipc_start_file@ function
* used to create the ZIP container file.
*
* The "format" value is a printf-style format string supporting "%d", "%s",
* and "%%" and is followed by any arguments needed by the string. Strings
* ("%s") are escaped as needed.
*/
int /* O - 0 on success, -1 on error */
zipcFileXMLPrintf(
zipc_file_t *zf, /* I - ZIP container file */
const char *format, /* I - Printf-style format string */
...) /* I - Additional arguments as needed */
{
int status = 0; /* Return status */
va_list ap; /* Pointer to additional arguments */
char buffer[65536], /* Buffer */
*bufend = buffer + sizeof(buffer) - 6,
/* End of buffer less "&quot;" */
*bufptr = buffer; /* Pointer into buffer */
const char *s; /* String pointer */
int d; /* Number */
va_start(ap, format);
while (*format && bufptr < bufend)
{
if (*format == '%')
{
format ++;
switch (*format)
{
case '%' : /* Substitute a single % */
format ++;
*bufptr++ = '%';
break;
case 'd' : /* Substitute a single integer */
format ++;
d = va_arg(ap, int);
snprintf(bufptr, bufend - bufptr, "%d", d);
bufptr += strlen(bufptr);
break;
case 's' : /* Substitute a single string */
format ++;
s = va_arg(ap, const char *);
while (*s && bufptr < bufend)
{
switch (*s)
{
case '&' : /* &amp; */
*bufptr++ = '&';
*bufptr++ = 'a';
*bufptr++ = 'm';
*bufptr++ = 'p';
*bufptr++ = ';';
break;
case '<' : /* &lt; */
*bufptr++ = '&';
*bufptr++ = 'l';
*bufptr++ = 't';
*bufptr++ = ';';
break;
case '>' : /* &gt; */
*bufptr++ = '&';
*bufptr++ = 'g';
*bufptr++ = 't';
*bufptr++ = ';';
break;
case '\"' : /* &quot; */
*bufptr++ = '&';
*bufptr++ = 'q';
*bufptr++ = 'u';
*bufptr++ = 'o';
*bufptr++ = 't';
*bufptr++ = ';';
break;
default :
*bufptr++ = *s;
break;
}
s ++;
}
if (*s)
{
format += strlen(format);
status = -1;
zf->zc->error = "Not enough memory to hold XML string.";
}
break;
default : /* Something else we don't support... */
format += strlen(format);
status = -1;
zf->zc->error = "Unsupported format character - only %%, %d, and %s are supported.";
break;
}
}
else
*bufptr++ = *format++;
}
va_end(ap);
if (*format)
{
status = -1;
zf->zc->error = "Not enough memory to hold XML string.";
}
if (bufptr > buffer)
status |= zipcFileWrite(zf, buffer, bufptr - buffer);
return (status);
}
/*
* 'zipcOpen()' - Open a ZIP container.
*
* Currently the only supported "mode" value is "w" (write).
*/
zipc_t * /* O - ZIP container */
zipcOpen(const char *filename, /* I - Filename of container */
const char *mode) /* I - Open mode ("w") */
{
zipc_t *zc; /* ZIP container */
/*
* Only support write mode for now...
*/
if (strcmp(mode, "w"))
{
errno = EINVAL;
return (NULL);
}
/*
* Allocate memory...
*/
if ((zc = calloc(1, sizeof(zipc_t))) != NULL)
{
time_t curtime; /* Current timestamp */
struct tm *curdate; /* Current date/time */
/*
* Open the container file...
*/
if ((zc->fp = fopen(filename, "wb")) == NULL)
{
free(zc);
return (NULL);
}
/*
* Get the current date/time and convert it to the packed MS-DOS format:
*
* Bits Description
* ------ -----------
* 0-4 Second
* 5-10 Minute
* 11-15 Hour
* 16-20 Day (1-31)
* 21-24 Month (1-12)
* 25-31 Years since 1980
*/
curtime = time(NULL);
curdate = gmtime(&curtime);
zc->modtime = curdate->tm_sec |
(curdate->tm_min << 5) |
(curdate->tm_hour << 11) |
(curdate->tm_mday << 16) |
((curdate->tm_mon + 1) << 21) |
((curdate->tm_year - 80) << 25);
}
return (zc);
}
/*
* 'zipc_add_file()' - Add a file to the ZIP container.
*/
static zipc_file_t * /* O - New file */
zipc_add_file(zipc_t *zc, /* I - ZIP container */
const char *filename, /* I - Name of file in container */
int compression) /* I - 0 = uncompressed, 1 = compressed */
{
zipc_file_t *temp; /* File(s) */
if (zc->num_files >= zc->alloc_files)
{
/*
* Allocate more files...
*/
zc->alloc_files += 10;
if (!zc->files)
temp = malloc(zc->alloc_files * sizeof(zipc_file_t));
else
temp = realloc(zc->files, zc->alloc_files * sizeof(zipc_file_t));
if (!temp)
{
zc->error = strerror(errno);
return (NULL);
}
zc->files = temp;
}
temp = zc->files + zc->num_files;
zc->num_files ++;
memset(temp, 0, sizeof(zipc_file_t));
strncpy(temp->filename, filename, sizeof(temp->filename) - 1);
temp->zc = zc;
temp->crc32 = crc32(0, NULL, 0);
temp->offset = ftell(zc->fp);
if (compression)
{
temp->flags = ZIPC_FLAG_CMAX;
temp->method = ZIPC_COMP_DEFLATE;
zc->stream.zalloc = (alloc_func)0;
zc->stream.zfree = (free_func)0;
zc->stream.opaque = (voidpf)0;
deflateInit2(&zc->stream, Z_BEST_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
zc->stream.next_out = (Bytef *)zc->buffer;
zc->stream.avail_out = sizeof(zc->buffer);
}
return (temp);
}
/*
* 'zipc_write()' - Write bytes to a ZIP container.
*/
static int /* I - 0 on success, -1 on error */
zipc_write(zipc_t *zc, /* I - ZIP container */
const void *buffer, /* I - Buffer to write */
size_t bytes) /* I - Number of bytes */
{
if (fwrite(buffer, bytes, 1, zc->fp))
return (0);
zc->error = strerror(ferror(zc->fp));
return (-1);
}
/*
* 'zipc_write_dir_header()' - Write a central directory file header.
*/
static int /* I - 0 on success, -1 on error */
zipc_write_dir_header(
zipc_t *zc, /* I - ZIP container */
zipc_file_t *zf) /* I - ZIP container file */
{
int status = 0; /* Return status */
size_t filenamelen = strlen(zf->filename);
/* Length of filename */
status |= zipc_write_u32(zc, ZIPC_DIR_HEADER);
status |= zipc_write_u16(zc, ZIPC_MADE_BY);
status |= zipc_write_u16(zc, ZIPC_VERSION);
status |= zipc_write_u16(zc, zf->flags);
status |= zipc_write_u16(zc, zf->method);
status |= zipc_write_u32(zc, zc->modtime);
status |= zipc_write_u32(zc, zf->crc32);
status |= zipc_write_u32(zc, zf->compressed_size);
status |= zipc_write_u32(zc, zf->uncompressed_size);
status |= zipc_write_u16(zc, filenamelen);
status |= zipc_write_u16(zc, 0); /* extra field length */
status |= zipc_write_u16(zc, 0); /* comment length */
status |= zipc_write_u16(zc, 0); /* disk number start */
status |= zipc_write_u16(zc, 0); /* internal file attributes */
status |= zipc_write_u32(zc, 0); /* external file attributes */
status |= zipc_write_u32(zc, zf->offset);
status |= zipc_write(zc, zf->filename, filenamelen);
return (status);
}
/*
* 'zipc_write_local_header()' - Write a local file header.
*/
static int /* I - 0 on success, -1 on error */
zipc_write_local_header(
zipc_t *zc, /* I - ZIP container */
zipc_file_t *zf) /* I - ZIP container file */
{
int status = 0; /* Return status */
unsigned flags = zf->flags; /* General purpose flags */
size_t filenamelen = strlen(zf->filename);
/* Length of filename */
if (zf->uncompressed_size == 0)
flags |= ZIPC_FLAG_DATA;
status |= zipc_write_u32(zc, ZIPC_LOCAL_HEADER);
status |= zipc_write_u16(zc, ZIPC_VERSION);
status |= zipc_write_u16(zc, flags);
status |= zipc_write_u16(zc, zf->method);
status |= zipc_write_u32(zc, zc->modtime);
status |= zipc_write_u32(zc, zf->uncompressed_size == 0 ? 0 : zf->crc32);
status |= zipc_write_u32(zc, zf->compressed_size);
status |= zipc_write_u32(zc, zf->uncompressed_size);
status |= zipc_write_u16(zc, filenamelen);
status |= zipc_write_u16(zc, 0); /* extra field length */
status |= zipc_write(zc, zf->filename, filenamelen);
return (status);
}
/*
* 'zipc_write_local_trailer()' - Write a local file trailer.
*/
static int /* I - 0 on success, -1 on error */
zipc_write_local_trailer(
zipc_t *zc, /* I - ZIP container */
zipc_file_t *zf) /* I - ZIP container file */
{
int status = 0; /* Return status */
status |= zipc_write_u32(zc, zf->crc32);
status |= zipc_write_u32(zc, zf->compressed_size);
status |= zipc_write_u32(zc, zf->uncompressed_size);
return (status);
}
/*
* 'zipc_write_u16()' - Write a 16-bit unsigned integer.
*/
static int /* I - 0 on success, -1 on error */
zipc_write_u16(zipc_t *zc, /* I - ZIP container */
unsigned value) /* I - Value to write */
{
unsigned char buffer[2]; /* Buffer */
buffer[0] = (unsigned char)value;
buffer[1] = (unsigned char)(value >> 8);
return (zipc_write(zc, buffer, sizeof(buffer)));
}
/*
* 'zipc_write_u32()' - Write a 32-bit unsigned integer.
*/
static int /* I - 0 on success, -1 on error */
zipc_write_u32(zipc_t *zc, /* I - ZIP container */
unsigned value) /* I - Value to write */
{
unsigned char buffer[4]; /* Buffer */
buffer[0] = (unsigned char)value;
buffer[1] = (unsigned char)(value >> 8);
buffer[2] = (unsigned char)(value >> 16);
buffer[3] = (unsigned char)(value >> 24);
return (zipc_write(zc, buffer, sizeof(buffer)));
}

@ -0,0 +1,71 @@
/*
* Header file for ZIP container mini-library.
*
* https://github.com/michaelrsweet/zipc
*
* Copyright 2017 by Michael R Sweet.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ZIPC_H
# define ZIPC_H
/*
* Include necessary headers...
*/
# include <sys/types.h>
/*
* Types...
*/
typedef struct _zipc_s zipc_t; /* ZIP container */
typedef struct _zipc_file_s zipc_file_t;/* File/directory in ZIP container */
/*
* Functions...
*/
extern int zipcClose(zipc_t *zc);
extern zipc_file_t *zipcCreateFile(zipc_t *zc, const char *filename, int compressed);
extern int zipcCreateFileWithString(zipc_t *zc, const char *filename, const char *contents);
extern const char *zipcError(zipc_t *zc);
extern int zipcFileFinish(zipc_file_t *zf);
extern int zipcFilePrintf(zipc_file_t *zf, const char *format, ...)
# ifdef __GNUC__
__attribute__ ((__format__ (__printf__, 2, 3)))
# endif /* __GNUC__ */
;
extern int zipcFilePuts(zipc_file_t *zf, const char *s);
extern int zipcFileWrite(zipc_file_t *zf, const void *data, size_t bytes);
extern int zipcFileXMLPrintf(zipc_file_t *zf, const char *format, ...)
# ifdef __GNUC__
__attribute__ ((__format__ (__printf__, 2, 3)))
# endif /* __GNUC__ */
;
extern zipc_t *zipcOpen(const char *filename, const char *mode);
#endif /* !ZIPC_H */
Loading…
Cancel
Save