From 7c2551e996077c630de32281c0626af0807e4ba1 Mon Sep 17 00:00:00 2001 From: Michael Sweet Date: Tue, 4 Apr 2017 16:16:17 -0400 Subject: [PATCH] Switch to using hand-rolled ZIP container "library" for EPUB files. --- Makefile.in | 24 +- configure | 133 ++--- configure.ac | 7 +- doc/mxml.man | 2 +- mxmldoc.c | 109 +--- xcode/mxml.xcodeproj/project.pbxproj | 14 +- zipc.c | 809 +++++++++++++++++++++++++++ zipc.h | 71 +++ 8 files changed, 1002 insertions(+), 167 deletions(-) create mode 100644 zipc.c create mode 100644 zipc.h diff --git a/Makefile.in b/Makefile.in index 79d20bb..2942328 100644 --- a/Makefile.in +++ b/Makefile.in @@ -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 diff --git a/configure b/configure index bbf89fc..26c942f 100755 --- a/configure +++ b/configure @@ -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 diff --git a/configure.ac b/configure.ac index 66d6cf2..9834d39 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/doc/mxml.man b/doc/mxml.man index a0f95db..c475f3f 100644 --- a/doc/mxml.man +++ b/doc/mxml.man @@ -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 diff --git a/mxmldoc.c b/mxmldoc.c index 7a4bdb6..0c12f66 100644 --- a/mxmldoc.c +++ b/mxmldoc.c @@ -20,6 +20,7 @@ #include "config.h" #include "mxml.h" +#include "zipc.h" #include #include #ifndef WIN32 @@ -32,14 +33,6 @@ extern char **environ; #endif /* __APPLE__ */ -#ifdef HAVE_ARCHIVE_H -# include -# include -#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__ */ /* diff --git a/xcode/mxml.xcodeproj/project.pbxproj b/xcode/mxml.xcodeproj/project.pbxproj index 78dcc54..f8e4694 100644 --- a/xcode/mxml.xcodeproj/project.pbxproj +++ b/xcode/mxml.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; + 272C00581E943266007EBCAC /* zipc.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = zipc.h; path = ../zipc.h; sourceTree = ""; }; + 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 = ""; @@ -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; }; diff --git a/zipc.c b/zipc.c new file mode 100644 index 0000000..b976e11 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include + + +/* + * 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 """ */ + *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 '&' : /* & */ + *bufptr++ = '&'; + *bufptr++ = 'a'; + *bufptr++ = 'm'; + *bufptr++ = 'p'; + *bufptr++ = ';'; + break; + case '<' : /* < */ + *bufptr++ = '&'; + *bufptr++ = 'l'; + *bufptr++ = 't'; + *bufptr++ = ';'; + break; + case '>' : /* > */ + *bufptr++ = '&'; + *bufptr++ = 'g'; + *bufptr++ = 't'; + *bufptr++ = ';'; + break; + case '\"' : /* " */ + *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))); +} diff --git a/zipc.h b/zipc.h new file mode 100644 index 0000000..ab2ca56 --- /dev/null +++ b/zipc.h @@ -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 + + +/* + * 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 */