Do not accept empty files as zip archives any longer.

Closes github issue #105.
diff --git a/NEWS.md b/NEWS.md
index 42ef46b..b4f6f7f 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -3,6 +3,7 @@
 
 * Avoid using umask() since it's not thread-safe.
 * Set close-on-exec flag when opening files.
+* Do not accept empty files as valid zip archives any longer.
 
 1.5.2 [2019-03-12]
 ==================
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index c7214f9..0fedd17 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -119,6 +119,7 @@
   zip_set_file_comment.c
   zip_set_file_compression.c
   zip_set_name.c
+  zip_source_accept_empty.c
   zip_source_begin_write.c
   zip_source_begin_write_cloning.c
   zip_source_buffer.c
diff --git a/lib/zip.h b/lib/zip.h
index 42c3e14..5688b5d 100644
--- a/lib/zip.h
+++ b/lib/zip.h
@@ -229,7 +229,8 @@
     ZIP_SOURCE_SUPPORTS,              /* check whether source supports command */
     ZIP_SOURCE_REMOVE,                /* remove file */
     ZIP_SOURCE_GET_COMPRESSION_FLAGS, /* get compression flags, internal only */
-    ZIP_SOURCE_BEGIN_WRITE_CLONING    /* like ZIP_SOURCE_BEGIN_WRITE, but keep part of original file */
+    ZIP_SOURCE_BEGIN_WRITE_CLONING,   /* like ZIP_SOURCE_BEGIN_WRITE, but keep part of original file */
+    ZIP_SOURCE_ACCEPT_EMPTY           /* whether empty files are valid archives */
 };
 typedef enum zip_source_cmd zip_source_cmd_t;
 
diff --git a/lib/zip_open.c b/lib/zip_open.c
index 593bfde..8680700 100644
--- a/lib/zip_open.c
+++ b/lib/zip_open.c
@@ -43,8 +43,7 @@
 typedef enum {
     EXISTS_ERROR = -1,
     EXISTS_NOT = 0,
-    EXISTS_EMPTY,
-    EXISTS_NONEMPTY,
+    EXISTS_OK
 } exists_t;
 static zip_t *_zip_allocate_new(zip_source_t *src, unsigned int flags, zip_error_t *error);
 static zip_int64_t _zip_checkcons(zip_t *za, zip_cdir_t *cdir, zip_error_t *error);
@@ -174,19 +173,16 @@
     }
     len = st.size;
 
-    /* treat empty files as empty archives */
-    if (len == 0) {
-	if ((za = _zip_allocate_new(src, flags, error)) == NULL) {
-	    return NULL;
-	}
-
-	return za;
-    }
 
     if ((za = _zip_allocate_new(src, flags, error)) == NULL) {
 	return NULL;
     }
 
+    /* treat empty files as empty archives */
+    if (len == 0 && zip_source_accept_empty(src)) {
+	return za;
+    }
+
     if ((cdir = _zip_find_central_dir(za, len)) == NULL) {
 	_zip_error_copy(error, &za->error);
 	/* keep src so discard does not get rid of it */
@@ -540,7 +536,7 @@
 	return EXISTS_ERROR;
     }
 
-    return (st.valid & ZIP_STAT_SIZE) && st.size == 0 ? EXISTS_EMPTY : EXISTS_NONEMPTY;
+    return EXISTS_OK;
 }
 
 
diff --git a/lib/zip_source_accept_empty.c b/lib/zip_source_accept_empty.c
new file mode 100644
index 0000000..07f56f6
--- /dev/null
+++ b/lib/zip_source_accept_empty.c
@@ -0,0 +1,52 @@
+/*
+  zip_source_accept_empty.c -- if empty source is a valid archive
+  Copyright (C) 2019 Dieter Baron and Thomas Klausner
+
+  This file is part of libzip, a library to manipulate ZIP archives.
+  The authors can be contacted at <libzip@nih.at>
+
+  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.
+  3. The names of the authors may not be used to endorse or promote
+     products derived from this software without specific prior
+     written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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 "zipint.h"
+
+
+bool
+zip_source_accept_empty(zip_source_t *src) {
+    int ret;
+
+    if ((zip_source_supports(src) & ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_ACCEPT_EMPTY)) == 0) {
+	if (ZIP_SOURCE_IS_LAYERED(src)) {
+	    return zip_source_accept_empty(src->src);
+	}
+	return true;
+    }
+
+    ret = _zip_source_call(src, NULL, 0, ZIP_SOURCE_ACCEPT_EMPTY);
+
+    return ret != 0;
+}
diff --git a/lib/zip_source_filep.c b/lib/zip_source_filep.c
index 51b34f2..4ad3fc2 100644
--- a/lib/zip_source_filep.c
+++ b/lib/zip_source_filep.c
@@ -205,6 +205,7 @@
 	}
     }
 
+    ctx->supports |= ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_ACCEPT_EMPTY);
 #ifdef CAN_CLONE
     if (ctx->supports & ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_BEGIN_WRITE)) {
 	ctx->supports |= ZIP_SOURCE_MAKE_COMMAND_BITMASK(ZIP_SOURCE_BEGIN_WRITE_CLONING);
@@ -374,6 +375,9 @@
     buf = (char *)data;
 
     switch (cmd) {
+    case ZIP_SOURCE_ACCEPT_EMPTY:
+	return 0;
+
     case ZIP_SOURCE_BEGIN_WRITE:
 	if (ctx->fname == NULL) {
 	    zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0);
@@ -390,6 +394,13 @@
 	return create_temp_output_cloning(ctx, len);
 #endif
 
+    case ZIP_SOURCE_CLOSE:
+	if (ctx->fname) {
+	    fclose(ctx->f);
+	    ctx->f = NULL;
+	}
+	return 0;
+
     case ZIP_SOURCE_COMMIT_WRITE: {
 	if (fclose(ctx->fout) < 0) {
 	    ctx->fout = NULL;
@@ -405,13 +416,6 @@
 	return 0;
     }
 
-    case ZIP_SOURCE_CLOSE:
-	if (ctx->fname) {
-	    fclose(ctx->f);
-	    ctx->f = NULL;
-	}
-	return 0;
-
     case ZIP_SOURCE_ERROR:
 	return zip_error_to_data(&ctx->error, data, len);
 
diff --git a/lib/zip_source_win32handle.c b/lib/zip_source_win32handle.c
index 3a2f52e..96406cd 100644
--- a/lib/zip_source_win32handle.c
+++ b/lib/zip_source_win32handle.c
@@ -127,6 +127,8 @@
 	ctx->supports = ZIP_SOURCE_SUPPORTS_SEEKABLE;
     }
 
+    ctx->supports |= ZIP_SOURCE_ACCEPT_EMPTY;
+
     if ((zs = zip_source_function_create(_win32_read_file, ctx, error)) == NULL) {
 	free(ctx->fname);
 	free(ctx);
@@ -148,6 +150,9 @@
     buf = (char *)data;
 
     switch (cmd) {
+    case ZIP_SOURCE_ACCEPT_EMPTY:
+	return 0;
+
     case ZIP_SOURCE_BEGIN_WRITE:
 	if (ctx->fname == NULL) {
 	    zip_error_set(&ctx->error, ZIP_ER_OPNOTSUPP, 0);
diff --git a/lib/zipint.h b/lib/zipint.h
index 76e8709..e52b60e 100644
--- a/lib/zipint.h
+++ b/lib/zipint.h
@@ -547,6 +547,7 @@
 
 void _zip_set_open_error(int *zep, const zip_error_t *err, int ze);
 
+bool zip_source_accept_empty(zip_source_t *src);
 zip_int64_t _zip_source_call(zip_source_t *src, void *data, zip_uint64_t length, zip_source_cmd_t command);
 bool _zip_source_eof(zip_source_t *);
 zip_source_t *_zip_source_file_or_p(const char *, FILE *, zip_uint64_t, zip_int64_t, const zip_stat_t *, zip_error_t *error);
diff --git a/man/zip_source_function.mdoc b/man/zip_source_function.mdoc
index a433b37..9a9cc4b 100644
--- a/man/zip_source_function.mdoc
+++ b/man/zip_source_function.mdoc
@@ -1,5 +1,5 @@
 .\" zip_source_function.mdoc -- create data source from function
-.\" Copyright (C) 2004-2018 Dieter Baron and Thomas Klausner
+.\" Copyright (C) 2004-2019 Dieter Baron and Thomas Klausner
 .\"
 .\" This file is part of libzip, a library to manipulate ZIP archives.
 .\" The authors can be contacted at <libzip@nih.at>
@@ -29,7 +29,7 @@
 .\" OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 .\" IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 .\"
-.Dd December 18, 2017
+.Dd September 17, 2019
 .Dt ZIP_SOURCE_FUNCTION 3
 .Os
 .Sh NAME
@@ -110,6 +110,10 @@
 and
 .Dv ZIP_SOURCE_REMOVE .
 .El
+.Ss Dv ZIP_SOURCE_ACCEPT_EMPTY
+Return 1 if an empty source should be accepted as a valid zip archive.
+This is the default if this command is not supported by a source.
+File system backed sources should return 0.
 .Ss Dv ZIP_SOURCE_BEGIN_WRITE
 Prepare the source for writing.
 Use this to create any temporary file(s).
diff --git a/regress/open_empty_2.test b/regress/open_empty_2.test
index 07a5358..bb35bc6 100644
--- a/regress/open_empty_2.test
+++ b/regress/open_empty_2.test
@@ -2,5 +2,6 @@
 program tryopen
 file testfile.txt testfile.txt testfile.txt
 args testfile.txt
-return 0
-stdout opening 'testfile.txt' succeeded, 0 entries
+return 1
+stdout opening 'testfile.txt' returned error 19
+stderr 1 errors