Hash file names to speed up some operations.

In particular, zip_file_add and zip_name_locate.
Add consistency check for duplicate file names,
now that it's cheap.
diff --git a/TODO b/TODO
index 124d79e..bbdb9d5 100644
--- a/TODO
+++ b/TODO
@@ -101,6 +101,7 @@
 
 Test Case Issues
 ================
+* unchange on added file
 * test seek in zip_source_crc
 * test cases for set_extra*, delete_extra*, *extra_field*
 * test cases for in memory archives
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
index 3bfc622..2a0962f 100644
--- a/lib/CMakeLists.txt
+++ b/lib/CMakeLists.txt
@@ -100,6 +100,7 @@
   zip_get_name.c
   zip_get_num_entries.c
   zip_get_num_files.c
+  zip_hash.c
   zip_io_util.c
   zip_memdup.c
   zip_name_locate.c
diff --git a/lib/Makefile.am b/lib/Makefile.am
index c43ed4b..8cd2e6e 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -58,6 +58,7 @@
 	zip_get_num_entries.c \
 	zip_get_num_files.c \
 	zip_get_name.c \
+	zip_hash.c \
 	zip_io_util.c \
 	zip_memdup.c \
 	zip_name_locate.c \
diff --git a/lib/zip_delete.c b/lib/zip_delete.c
index b3e7abb..7be0926 100644
--- a/lib/zip_delete.c
+++ b/lib/zip_delete.c
@@ -38,6 +38,8 @@
 ZIP_EXTERN int
 zip_delete(zip_t *za, zip_uint64_t idx)
 {
+    const char *name;
+
     if (idx >= za->nentry) {
 	zip_error_set(&za->error, ZIP_ER_INVAL, 0);
 	return -1;
@@ -48,6 +50,14 @@
 	return -1;
     }
 
+    if ((name=_zip_get_name(za, idx, 0, &za->error)) == NULL) {
+	return -1;
+    }
+
+    if (!_zip_hash_delete(za->names, (const zip_uint8_t *)name, &za->error)) {
+	return -1;
+    }
+
     /* allow duplicate file names, because the file will
      * be removed directly afterwards */
     if (_zip_unchange(za, idx, 1) != 0)
diff --git a/lib/zip_discard.c b/lib/zip_discard.c
index db22370..cf12215 100644
--- a/lib/zip_discard.c
+++ b/lib/zip_discard.c
@@ -58,6 +58,8 @@
     _zip_string_free(za->comment_orig);
     _zip_string_free(za->comment_changes);
 
+    _zip_hash_free(za->names);
+
     if (za->entry) {
 	for (i=0; i<za->nentry; i++)
 	    _zip_entry_finalize(za->entry+i);
diff --git a/lib/zip_hash.c b/lib/zip_hash.c
new file mode 100644
index 0000000..ec6e336
--- /dev/null
+++ b/lib/zip_hash.c
@@ -0,0 +1,263 @@
+/*
+  zip_hash.c -- hash table string -> uint64
+  Copyright (C) 2015 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 <stdlib.h>
+#include <string.h>
+#include "zipint.h"
+
+struct zip_hash_entry {
+    const zip_uint8_t *name;
+    zip_int64_t orig_index;
+    zip_int64_t current_index;
+    struct zip_hash_entry *next;
+};
+typedef struct zip_hash_entry zip_hash_entry_t;
+
+struct zip_hash {
+    zip_uint16_t table_size;
+    zip_hash_entry_t **table;
+};
+
+zip_hash_t *
+_zip_hash_new(zip_uint16_t table_size, zip_error_t *error)
+{
+    zip_hash_t *hash;
+
+    if (table_size == 0) {
+	zip_error_set(error, ZIP_ER_INTERNAL, 0);
+	return NULL;
+    }
+
+    if ((hash=(zip_hash_t *)malloc(sizeof(zip_hash_t))) == NULL) {
+	zip_error_set(error, ZIP_ER_MEMORY, 0);
+	return NULL;
+    }
+    hash->table_size = table_size;
+    if ((hash->table=(zip_hash_entry_t**)calloc(table_size, sizeof(zip_hash_entry_t *))) == NULL) {
+	free(hash);
+	zip_error_set(error, ZIP_ER_MEMORY, 0);
+	return NULL;
+    }
+
+    return hash;
+}
+
+static void
+_free_list(zip_hash_entry_t *entry)
+{
+    zip_hash_entry_t *next;
+    do {
+	next = entry->next;
+	free(entry);
+	entry = next;
+    } while (entry != NULL);
+}
+
+void
+_zip_hash_free(zip_hash_t *hash)
+{
+    zip_uint16_t i;
+
+    if (hash == NULL) {
+	return;
+    }
+
+    for (i=0; i<hash->table_size; i++) {
+	if (hash->table[i] != NULL) {
+	    _free_list(hash->table[i]);
+	}
+    }
+    free(hash->table);
+    free(hash);
+}
+
+static zip_uint16_t
+_hash_string(const zip_uint8_t *name, zip_uint16_t size)
+{
+#define HASH_MULTIPLIER 33
+    zip_uint16_t value = 5381;
+
+    if (name == NULL)
+	return 0;
+
+    while (*name != 0) {
+	value = (zip_uint16_t)(((value * HASH_MULTIPLIER) + (zip_uint8_t)*name) % size);
+	name++;
+    }
+
+    return value;
+}
+
+/* insert into hash, return error on existence or memory issues */
+bool
+_zip_hash_add(zip_hash_t *hash, const zip_uint8_t *name, zip_uint64_t index, zip_flags_t flags, zip_error_t *error)
+{
+    zip_uint16_t hash_value;
+    zip_hash_entry_t *entry;
+
+    if (hash == NULL || name == NULL || index > ZIP_INT64_MAX) {
+	zip_error_set(error, ZIP_ER_INVAL, 0);
+	return false;
+    }
+
+    hash_value = _hash_string(name, hash->table_size);
+    for (entry = hash->table[hash_value]; entry != NULL; entry = entry->next) {
+	if (strcmp((const char *)name, (const char *)entry->name) == 0) {
+	    if (((flags & ZIP_FL_UNCHANGED) && entry->orig_index != -1) || entry->current_index != -1) {
+		zip_error_set(error, ZIP_ER_EXISTS, 0);
+		return false;
+	    }
+	    else {
+		break;
+	    }
+	}
+    }
+
+    if (entry == NULL) {
+	if ((entry=(zip_hash_entry_t *)malloc(sizeof(zip_hash_entry_t))) == NULL) {
+	    zip_error_set(error, ZIP_ER_MEMORY, 0);
+	    return false;
+	}
+	entry->name = name;
+	entry->next = hash->table[hash_value];
+	hash->table[hash_value] = entry;
+	entry->orig_index = -1;
+    }
+
+    if (flags & ZIP_FL_UNCHANGED) {
+	entry->orig_index = (zip_int64_t)index;
+    }
+    entry->current_index = (zip_int64_t)index;
+
+    return true;
+}
+
+/* remove entry from hash, error if not found */
+bool
+_zip_hash_delete(zip_hash_t *hash, const zip_uint8_t *name, zip_error_t *error)
+{
+    zip_uint16_t hash_value;
+    zip_hash_entry_t *entry, *previous;
+
+    if (hash == NULL || name == NULL) {
+	zip_error_set(error, ZIP_ER_INVAL, 0);
+	return false;
+    }
+
+    hash_value = _hash_string(name, hash->table_size);
+    previous = NULL;
+    entry = hash->table[hash_value];
+    while (entry) {
+	if (strcmp((const char *)name, (const char *)entry->name) == 0) {
+	    if (entry->orig_index == -1) {
+		if (previous) {
+		    previous->next = entry->next;
+		}
+		else {
+		    hash->table[hash_value] = entry->next;
+		}
+		free(entry);
+	    }
+	    else {
+		entry->current_index = -1;
+	    }
+	    return true;
+	}
+	previous = entry;
+	entry = entry->next;
+    };
+
+    zip_error_set(error, ZIP_ER_NOENT, 0);
+    return false;
+}
+
+/* find value for entry in hash, -1 if not found */
+zip_int64_t
+_zip_hash_lookup(zip_hash_t *hash, const zip_uint8_t *name, zip_flags_t flags, zip_error_t *error)
+{
+    zip_uint16_t hash_value;
+    zip_hash_entry_t *entry;
+
+    if (hash == NULL || name == NULL) {
+	zip_error_set(error, ZIP_ER_INVAL, 0);
+	return -1;
+    }
+
+    hash_value = _hash_string(name, hash->table_size);
+    for (entry = hash->table[hash_value]; entry != NULL; entry = entry->next) {
+	if (strcmp((const char *)name, (const char *)entry->name) == 0) {
+	    if (flags & ZIP_FL_UNCHANGED) {
+		if (entry->orig_index != -1) {
+		    return entry->orig_index;
+		}
+	    }
+	    else {
+		if (entry->current_index != -1) {
+		    return entry->current_index;
+		}
+	    }
+	    break;
+	}
+    }
+
+    zip_error_set(error, ZIP_ER_NOENT, 0);
+    return -1;    
+}
+
+void
+_zip_hash_revert(zip_hash_t *hash)
+{
+    zip_uint16_t i;
+    zip_hash_entry_t *entry, *previous;
+    
+    for (i = 0; i < hash->table_size; i++) {
+	previous = NULL;
+	entry = hash->table[i];
+	while (entry) {
+	    if (entry->orig_index == -1) {
+		if (previous) {
+		    previous->next = entry->next;
+		}
+		else {
+		    hash->table[i] = entry->next;
+		}
+		free(entry);
+	    }
+	    else {
+		entry->current_index = entry->orig_index;
+	    }
+	    previous = entry;
+	    entry = entry->next;
+	}
+    }
+}
diff --git a/lib/zip_name_locate.c b/lib/zip_name_locate.c
index 820ea0c..896132c 100644
--- a/lib/zip_name_locate.c
+++ b/lib/zip_name_locate.c
@@ -62,27 +62,33 @@
 	return -1;
     }
 
-    cmp = (flags & ZIP_FL_NOCASE) ? strcasecmp : strcmp;
+    if (flags & (ZIP_FL_NOCASE|ZIP_FL_NODIR|ZIP_FL_ENC_CP437)) {
+	/* can't use hash table */
+	cmp = (flags & ZIP_FL_NOCASE) ? strcasecmp : strcmp;
 
-    for (i=0; i<za->nentry; i++) {
-	fn = _zip_get_name(za, i, flags, error);
-
-	/* newly added (partially filled) entry or error */
-	if (fn == NULL)
-	    continue;
-	
-	if (flags & ZIP_FL_NODIR) {
-	    p = strrchr(fn, '/');
-	    if (p)
-		fn = p+1;
+	for (i=0; i<za->nentry; i++) {
+	    fn = _zip_get_name(za, i, flags, error);
+	    
+	    /* newly added (partially filled) entry or error */
+	    if (fn == NULL)
+		continue;
+	    
+	    if (flags & ZIP_FL_NODIR) {
+		p = strrchr(fn, '/');
+		if (p)
+		    fn = p+1;
+	    }
+	    
+	    if (cmp(fname, fn) == 0) {
+		_zip_error_clear(error);
+		return (zip_int64_t)i;
+	    }
 	}
 
-	if (cmp(fname, fn) == 0) {
-	    _zip_error_clear(error);
-	    return (zip_int64_t)i;
-	}
+	zip_error_set(error, ZIP_ER_NOENT, 0);
+	return -1;
     }
-
-    zip_error_set(error, ZIP_ER_NOENT, 0);
-    return -1;
+    else {
+	return _zip_hash_lookup(za->names, (const zip_uint8_t *)fname, flags, error);
+    }
 }
diff --git a/lib/zip_new.c b/lib/zip_new.c
index d54a247..905e45c 100644
--- a/lib/zip_new.c
+++ b/lib/zip_new.c
@@ -52,6 +52,11 @@
 	return NULL;
     }
 
+    if ((za->names = _zip_hash_new(ZIP_HASH_TABLE_SIZE, error)) == NULL) {
+	free(za);
+	return NULL;
+    }
+
     za->src = NULL;
     za->open_flags = 0;
     zip_error_init(&za->error);
diff --git a/lib/zip_open.c b/lib/zip_open.c
index 1f5e4d8..3f866a9 100644
--- a/lib/zip_open.c
+++ b/lib/zip_open.c
@@ -183,7 +183,7 @@
     zip_t *za;
     zip_cdir_t *cdir;
     struct zip_stat st;
-    zip_uint64_t len;
+    zip_uint64_t len, idx;
 
     zip_stat_init(&st);
     if (zip_source_stat(src, &st) < 0) {
@@ -222,11 +222,31 @@
     za->nentry = cdir->nentry;
     za->nentry_alloc = cdir->nentry_alloc;
     za->comment_orig = cdir->comment;
-    
-    za->ch_flags = za->flags;
 
     free(cdir);
 
+    for (idx = 0; idx < za->nentry; idx++) {
+	const zip_uint8_t *name = _zip_string_get(za->entry[idx].orig->filename, NULL, 0, error);
+	if (name == NULL) {
+		/* keep src so discard does not get rid of it */
+		zip_source_keep(src);
+		zip_discard(za);
+		return NULL;
+	}
+	
+	if (_zip_hash_add(za->names, name, idx, ZIP_FL_UNCHANGED, &za->error) == false) {
+	    if (za->error.zip_err != ZIP_ER_EXISTS || (flags & ZIP_CHECKCONS)) {
+		_zip_error_copy(error, &za->error);
+		/* keep src so discard does not get rid of it */
+		zip_source_keep(src);
+		zip_discard(za);
+		return NULL;
+	    }
+	}
+    }
+    
+    za->ch_flags = za->flags;
+
     return za;
 }
 
diff --git a/lib/zip_set_name.c b/lib/zip_set_name.c
index 5a10381..fd60e16 100644
--- a/lib/zip_set_name.c
+++ b/lib/zip_set_name.c
@@ -43,8 +43,10 @@
 {
     zip_entry_t *e;
     zip_string_t *str;
-    int changed;
+    bool same_as_orig;
     zip_int64_t i;
+    const zip_uint8_t *old_name, *new_name;
+    zip_string_t *old_str;
 
     if (idx >= za->nentry) {
 	zip_error_set(&za->error, ZIP_ER_INVAL, 0);
@@ -81,35 +83,76 @@
 
     e = za->entry+idx;
 
-    if (e->changes) {
-	_zip_string_free(e->changes->filename);
-	e->changes->filename = NULL;
-	e->changes->changed &= ~ZIP_DIRENT_FILENAME;
+    if (e->orig)
+	same_as_orig = _zip_string_equal(e->orig->filename, str);
+    else
+	same_as_orig = false;
+
+    if (!same_as_orig && e->changes == NULL) {
+	if ((e->changes=_zip_dirent_clone(e->orig)) == NULL) {
+	    zip_error_set(&za->error, ZIP_ER_MEMORY, 0);
+	    _zip_string_free(str);
+	    return -1;
+	}
     }
 
-    if (e->orig)
-	changed = !_zip_string_equal(e->orig->filename, str);
-    else
-	changed = 1;
-	
-    if (changed) {
-        if (e->changes == NULL) {
-            if ((e->changes=_zip_dirent_clone(e->orig)) == NULL) {
-                zip_error_set(&za->error, ZIP_ER_MEMORY, 0);
-		_zip_string_free(str);
-                return -1;
-            }
-        }
-        e->changes->filename = str;
-        e->changes->changed |= ZIP_DIRENT_FILENAME;
+    if ((new_name = _zip_string_get(same_as_orig ? e->orig->filename : str, NULL, 0, &za->error)) == NULL) {
+	_zip_string_free(str);
+	return -1;
+    }
+
+    if (e->changes) {
+	old_str = e->changes->filename;
+    }
+    else if (e->orig) {
+	old_str = e->orig->filename;
     }
     else {
-	_zip_string_free(str);
-	if (e->changes && e->changes->changed == 0) {
-	    _zip_dirent_free(e->changes);
-	    e->changes = NULL;
+	old_str = NULL;
+    }
+    
+    if (old_str) {
+	if ((old_name = _zip_string_get(old_str, NULL, 0, &za->error)) == NULL) {
+	    _zip_string_free(str);
+	    return -1;
 	}
     }
+    else {
+	old_name = NULL;
+    }
+
+    if (_zip_hash_add(za->names, new_name, idx, 0, &za->error) == false) {
+	_zip_string_free(str);
+	return -1;
+    }
+    if (old_name) {
+	_zip_hash_delete(za->names, old_name, NULL);
+    }
+
+    if (same_as_orig) {
+	if (e->changes) {
+	    if (e->changes->changed & ZIP_DIRENT_FILENAME) {
+		_zip_string_free(e->changes->filename);
+		e->changes->changed &= ~ZIP_DIRENT_FILENAME;
+		if (e->changes->changed == 0) {
+		    _zip_dirent_free(e->changes);
+		    e->changes = NULL;
+		}
+		else {
+		    /* TODO: what if not cloned? can that happen? */
+		    e->changes->filename = e->orig->filename;
+		}
+	    }
+	}
+	_zip_string_free(str);
+    }
+    else {
+	if (e->changes->changed & ZIP_DIRENT_FILENAME) {
+	    _zip_string_free(e->changes->filename);
+	}
+	e->changes->changed |= ZIP_DIRENT_FILENAME;
+	e->changes->filename = str;
+    }
 
     return 0;
 }
diff --git a/lib/zip_unchange.c b/lib/zip_unchange.c
index 6c8a495..c753712 100644
--- a/lib/zip_unchange.c
+++ b/lib/zip_unchange.c
@@ -48,6 +48,7 @@
 _zip_unchange(zip_t *za, zip_uint64_t idx, int allow_duplicates)
 {
     zip_int64_t i;
+    const char *orig_name, *changed_name;
     
     if (idx >= za->nentry) {
 	zip_error_set(&za->error, ZIP_ER_INVAL, 0);
@@ -55,9 +56,32 @@
     }
 
     if (!allow_duplicates && za->entry[idx].changes && (za->entry[idx].changes->changed & ZIP_DIRENT_FILENAME)) {
-	i = _zip_name_locate(za, _zip_get_name(za, idx, ZIP_FL_UNCHANGED, NULL), 0, NULL);
-	if (i >= 0 && (zip_uint64_t)i != idx) {
-	    zip_error_set(&za->error, ZIP_ER_EXISTS, 0);
+	if (za->entry[idx].orig != NULL) {
+	    if ((orig_name=_zip_get_name(za, idx, ZIP_FL_UNCHANGED, &za->error)) == NULL) {
+		return -1;
+	    }
+
+	    i = _zip_name_locate(za, orig_name, 0, NULL);
+	    if (i >= 0 && (zip_uint64_t)i != idx) {
+		zip_error_set(&za->error, ZIP_ER_EXISTS, 0);
+		return -1;
+	    }
+	}
+	else {
+	    orig_name = NULL;
+	}
+	    
+	if ((changed_name=_zip_get_name(za, idx, 0, &za->error)) == NULL) {
+	    return -1;
+	}
+
+	if (orig_name) {
+	    if (_zip_hash_add(za->names, (const zip_uint8_t *)orig_name, idx, 0, &za->error) == false) {
+		return -1;
+	    }
+	}
+	if (_zip_hash_delete(za->names, (const zip_uint8_t *)changed_name, &za->error) == false) {
+	    _zip_hash_delete(za->names, (const zip_uint8_t *)orig_name, NULL);
 	    return -1;
 	}
     }
diff --git a/lib/zip_unchange_all.c b/lib/zip_unchange_all.c
index 6007683..a72c068 100644
--- a/lib/zip_unchange_all.c
+++ b/lib/zip_unchange_all.c
@@ -43,6 +43,8 @@
     int ret;
     zip_uint64_t i;
 
+    _zip_hash_revert(za->names);
+    
     ret = 0;
     for (i=0; i<za->nentry; i++)
 	ret |= _zip_unchange(za, i, 1);
diff --git a/lib/zipint.h b/lib/zipint.h
index a53d62c..91ae88b 100644
--- a/lib/zipint.h
+++ b/lib/zipint.h
@@ -266,13 +266,19 @@
 
 typedef enum zip_encoding_type zip_encoding_type_t;
 
+#ifndef ZIP_HASH_TABLE_SIZE
+#define ZIP_HASH_TABLE_SIZE 8192
+#endif
+
+struct zip_hash;
+
 typedef struct zip_cdir zip_cdir_t;
 typedef struct zip_dirent zip_dirent_t;
 typedef struct zip_entry zip_entry_t;
 typedef struct zip_extra_field zip_extra_field_t;
 typedef struct zip_string zip_string_t;
 typedef struct zip_buffer zip_buffer_t;
-
+typedef struct zip_hash zip_hash_t;
 
 /* zip archive, part of API */
 
@@ -287,7 +293,7 @@
     char *default_password;		/* password used when no other supplied */
 
     zip_string_t *comment_orig;         /* archive comment */
-    zip_string_t *comment_changes;  /* changed archive comment */
+    zip_string_t *comment_changes;	/* changed archive comment */
     bool comment_changed;		/* whether archive comment was changed */
 
     zip_uint64_t nentry;		/* number of entries */
@@ -297,6 +303,8 @@
     unsigned int nopen_source;		/* number of open sources using archive */
     unsigned int nopen_source_alloc;	/* number of sources allocated */
     zip_source_t **open_source;         /* open sources using archive */
+
+    zip_hash_t *names;			/* hash table for name lookup */
     
     char *tempdir;                      /* custom temp dir (needed e.g. for OS X sandboxing) */
 };
@@ -323,7 +331,7 @@
 struct zip_dirent {
     zip_uint32_t changed;
     bool local_extra_fields_read;	/*      whether we already read in local header extra fields */
-    bool cloned;                         /*      whether this instance is cloned, and thus shares non-changed strings */
+    bool cloned;                        /*      whether this instance is cloned, and thus shares non-changed strings */
 
     zip_uint16_t version_madeby;	/* (c)  version of creator */
     zip_uint16_t version_needed;	/* (cl) version needed to extract */
@@ -516,6 +524,13 @@
 enum zip_encoding_type _zip_guess_encoding(zip_string_t *, enum zip_encoding_type);
 zip_uint8_t *_zip_cp437_to_utf8(const zip_uint8_t * const, zip_uint32_t, zip_uint32_t *, zip_error_t *);
 
+bool _zip_hash_add(zip_hash_t *hash, const zip_uint8_t *name, zip_uint64_t index, zip_flags_t flags, zip_error_t *error);
+bool _zip_hash_delete(zip_hash_t *hash, const zip_uint8_t *key, zip_error_t *error);
+void _zip_hash_free(zip_hash_t *hash);
+zip_int64_t _zip_hash_lookup(zip_hash_t *hash, const zip_uint8_t *name, zip_flags_t flags, zip_error_t *error);
+zip_hash_t *_zip_hash_new(zip_uint16_t hash_size, zip_error_t *error);
+void _zip_hash_revert(zip_hash_t *hash);
+
 zip_t *_zip_open(zip_source_t *, unsigned int, zip_error_t *);
 
 int _zip_read(zip_source_t *src, zip_uint8_t *data, zip_uint64_t length, zip_error_t *error);
diff --git a/regress/Makefile.am b/regress/Makefile.am
index fdccb90..338516f 100644
--- a/regress/Makefile.am
+++ b/regress/Makefile.am
@@ -223,10 +223,6 @@
 	zip64_creation.test \
 	zip64_stored_creation.test
 
-XFAIL_TESTS= \
-	open_filename_duplicate_consistency.test \
-	open_filename_duplicate_empty_consistency.test
-
 AM_CPPFLAGS=-I${top_srcdir}/lib -I../lib -I${top_srcdir}/src
 LDADD=${top_builddir}/lib/libzip.la
 
diff --git a/regress/open_filename_duplicate.test b/regress/open_filename_duplicate.test
index c8cc727..5204db6 100644
--- a/regress/open_filename_duplicate.test
+++ b/regress/open_filename_duplicate.test
@@ -1,6 +1,6 @@
 # zip_open: file opens fine even though same file name appears twice
 program tryopen
-args filename_duplicate.zip
+args filename_duplicate.zzip
 return 0
-file filename_duplicate.zip filename_duplicate.zip filename_duplicate.zip
-stdout opening 'filename_duplicate.zip' succeeded, 2 entries
+file filename_duplicate.zzip filename_duplicate.zip filename_duplicate.zip
+stdout opening 'filename_duplicate.zzip' succeeded, 2 entries
diff --git a/regress/open_filename_duplicate_consistency.test b/regress/open_filename_duplicate_consistency.test
index a997972..e82cf75 100644
--- a/regress/open_filename_duplicate_consistency.test
+++ b/regress/open_filename_duplicate_consistency.test
@@ -1,6 +1,7 @@
 # zip_open: file opens fine even though same file name appears twice
 program tryopen
-args -c filename_duplicate.zip
-return 0
-file filename_duplicate.zip filename_duplicate.zip filename_duplicate.zip
-stderr some error about a duplicate file name
+args -c filename_duplicate.zzip
+return 1
+file filename_duplicate.zzip filename_duplicate.zip filename_duplicate.zip
+stdout opening 'filename_duplicate.zzip' returned error 10
+stderr 1 errors
diff --git a/regress/open_filename_duplicate_empty.test b/regress/open_filename_duplicate_empty.test
index 505916b..ea143b6 100644
--- a/regress/open_filename_duplicate_empty.test
+++ b/regress/open_filename_duplicate_empty.test
@@ -1,6 +1,6 @@
 # zip_open: file opens fine even though same file name (empty file name) appears twice
 program tryopen
-args filename_duplicate_empty.zip
+args filename_duplicate_empty.zzip
 return 0
-file filename_duplicate_empty.zip filename_duplicate_empty.zip filename_duplicate_empty.zip
-stdout opening 'filename_duplicate_empty.zip' succeeded, 2 entries
+file filename_duplicate_empty.zzip filename_duplicate_empty.zip filename_duplicate_empty.zip
+stdout opening 'filename_duplicate_empty.zzip' succeeded, 2 entries
diff --git a/regress/open_filename_duplicate_empty_consistency.test b/regress/open_filename_duplicate_empty_consistency.test
index 43bb43e..6570195 100644
--- a/regress/open_filename_duplicate_empty_consistency.test
+++ b/regress/open_filename_duplicate_empty_consistency.test
@@ -1,6 +1,7 @@
 # zip_open: file opens fine even though same file name (empty file name) appears twice
 program tryopen
-args -c filename_duplicate_empty.zip
+args -c filename_duplicate_empty.zzip
 return 1
-file filename_duplicate_empty.zip filename_duplicate_empty.zip filename_duplicate_empty.zip
-stdout some error about duplicate file names
+file filename_duplicate_empty.zzip filename_duplicate_empty.zip filename_duplicate_empty.zip
+stdout opening 'filename_duplicate_empty.zzip' returned error 10
+stderr 1 errors