/*-
 * Copyright (c) 2009,2010 Michihiro NAKAJIMA
 * All rights reserved.
 *
 * 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 AUTHOR(S) ``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 AUTHOR(S) 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 "test.h"

/*
 * Check that an ISO 9660 image is correctly created.
 */
static void
add_entry(struct archive *a, const char *fname, const char *sym)
{
	struct archive_entry *ae;

	assert((ae = archive_entry_new()) != NULL);
	archive_entry_set_birthtime(ae, 2, 20);
	archive_entry_set_atime(ae, 3, 30);
	archive_entry_set_ctime(ae, 4, 40);
	archive_entry_set_mtime(ae, 5, 50);
	archive_entry_copy_pathname(ae, fname);
	if (sym != NULL)
		archive_entry_set_symlink(ae, sym);
	archive_entry_set_mode(ae, S_IFREG | 0555);
	archive_entry_set_size(ae, 0);
	assertEqualIntA(a, ARCHIVE_OK, archive_write_header(a, ae));
	archive_entry_free(ae);
}

struct fns {
	size_t	maxlen;
	size_t	longest_len;
	size_t	maxflen;
	size_t	maxelen;
	size_t	alloc;
	int	cnt;
	char	**names;
	int	opt;
#define	UPPER_CASE_ONLY	0x00001
#define	ONE_DOT		0x00002
#define	ALLOW_LDOT	0x00004
};

enum vtype {
	ROCKRIDGE,
	JOLIET,
	ISO9660
};

/*
 * Verify file
 */
static void
verify_file(struct archive *a, enum vtype type, struct fns *fns)
{
	struct archive_entry *ae;
	int i;

	assertEqualIntA(a, 0, archive_read_next_header(a, &ae));
	if (type == ROCKRIDGE) {
		assertEqualInt(2, archive_entry_birthtime(ae));
		assertEqualInt(3, archive_entry_atime(ae));
		assertEqualInt(4, archive_entry_ctime(ae));
	} else {
		assertEqualInt(0, archive_entry_birthtime_is_set(ae));
		assertEqualInt(5, archive_entry_atime(ae));
		assertEqualInt(5, archive_entry_ctime(ae));
	}
	assertEqualInt(5, archive_entry_mtime(ae));
	if (type == ROCKRIDGE)
		assert((S_IFREG | 0555) == archive_entry_mode(ae));
	else
		assert((S_IFREG | 0400) == archive_entry_mode(ae));
	assertEqualInt(0, archive_entry_size(ae));

	/*
	 * Check if the same filename does not appear.
	 */
	for (i = 0; i < fns->cnt; i++) {
		const char *p;
		const char *pathname = archive_entry_pathname(ae);
		const char *symlinkname = archive_entry_symlink(ae);
		size_t length;

		if (symlinkname != NULL) {
			length = strlen(symlinkname);
			assert(length == 1 || length == 128 || length == 255);
			assertEqualInt(symlinkname[length-1], 'x');
		}
		failure("Found duplicate for %s", pathname);
		assert(strcmp(fns->names[i], pathname) != 0);
		assert((length = strlen(pathname)) <= fns->maxlen);
		if (length > fns->longest_len)
			fns->longest_len = length;
		p = strrchr(pathname, '.');
		if (p != NULL) {
			/* Check a length of file name. */
			assert((size_t)(p - pathname) <= fns->maxflen);
			/* Check a length of file extension. */
			assert(strlen(p+1) <= fns->maxelen);
			if (fns->opt & ONE_DOT) {
				/* Do not have multi dot. */
				assert(strchr(pathname, '.') == p);
			}
		}
		for (p = pathname; *p; p++) {
			if (fns->opt & UPPER_CASE_ONLY) {
				/* Do not have any lower-case character. */
				assert(*p < 'a' || *p > 'z');
			} else
				break;
		}
		if ((fns->opt & ALLOW_LDOT) == 0)
			/* Do not have a dot at the first position. */
			assert(*pathname != '.');
	}
	/* Save the filename which is appeared to use above next time. */
	fns->names[fns->cnt++] = strdup(archive_entry_pathname(ae));
}

static void
verify(unsigned char *buff, size_t used, enum vtype type, struct fns *fns)
{
	struct archive *a;
	struct archive_entry *ae;
	size_t i;

	/*
	 * Read ISO image.
	 */
	assert((a = archive_read_new()) != NULL);
	assertEqualIntA(a, 0, archive_read_support_format_all(a));
	assertEqualIntA(a, 0, archive_read_support_filter_all(a));
	if (type >= 1)
		assertA(0 == archive_read_set_option(a, NULL, "rockridge",
		    NULL));
	if (type >= 2)
		assertA(0 == archive_read_set_option(a, NULL, "joliet",
		    NULL));
	assertEqualIntA(a, 0, archive_read_open_memory(a, buff, used));

	/*
	 * Read Root Directory
	 * Root Directory entry must be in ISO image.
	 */
	assertEqualIntA(a, 0, archive_read_next_header(a, &ae));
	assertEqualInt(archive_entry_atime(ae), archive_entry_ctime(ae));
	assertEqualInt(archive_entry_atime(ae), archive_entry_mtime(ae));
	assertEqualString(".", archive_entry_pathname(ae));
	switch (type) {
	case ROCKRIDGE:
		assert((S_IFDIR | 0555) == archive_entry_mode(ae));
		break;
	case JOLIET:
		assert((S_IFDIR | 0700) == archive_entry_mode(ae));
		break;
	case ISO9660:
		assert((S_IFDIR | 0700) == archive_entry_mode(ae));
		break;
	}

	/*
	 * Verify file status.
	 */
	memset(fns->names, 0, sizeof(char *) * fns->alloc);
	fns->cnt = 0;
	for (i = 0; i < fns->alloc; i++)
		verify_file(a, type, fns);
	for (i = 0; i < fns->alloc; i++)
		free(fns->names[i]);
	assertEqualInt((int)fns->longest_len, (int)fns->maxlen);

	/*
	 * Verify the end of the archive.
	 */
	assertEqualIntA(a, ARCHIVE_EOF, archive_read_next_header(a, &ae));
	assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
	assertEqualIntA(a, ARCHIVE_OK, archive_read_free(a));
}

static int
create_iso_image(unsigned char *buff, size_t buffsize, size_t *used,
    const char *opt)
{
	struct archive *a;
	int i, l, fcnt;
	const int lens[] = {
	    0, 1, 3, 5, 7, 8, 9, 29, 30, 31, 32,
		62, 63, 64, 65, 101, 102, 103, 104,
	    191, 192, 193, 194, 204, 205, 206, 207, 208,
		252, 253, 254, 255,
	    -1 };
	char fname1[256];
	char fname2[256];
	char sym1[2];
	char sym128[129];
	char sym255[256];

	/* ISO9660 format: Create a new archive in memory. */
	assert((a = archive_write_new()) != NULL);
	assertA(0 == archive_write_set_format_iso9660(a));
	assertA(0 == archive_write_add_filter_none(a));
	assertA(0 == archive_write_set_option(a, NULL, "pad", NULL));
	if (opt)
		assertA(0 == archive_write_set_options(a, opt));
	assertA(0 == archive_write_set_bytes_per_block(a, 1));
	assertA(0 == archive_write_set_bytes_in_last_block(a, 1));
	assertA(0 == archive_write_open_memory(a, buff, buffsize, used));

	sym1[0] = 'x';
	sym1[1] = '\0';
	for (i = 0; i < (int)sizeof(sym128)-2; i++)
		sym128[i] = 'a';
	sym128[sizeof(sym128)-2] = 'x';
	sym128[sizeof(sym128)-1] = '\0';
	for (i = 0; i < (int)sizeof(sym255)-2; i++)
		sym255[i] = 'a';
	sym255[sizeof(sym255)-2] = 'x';
	sym255[sizeof(sym255)-1] = '\0';

	fcnt = 0;
	for (i = 0; lens[i] >= 0; i++) {
		for (l = 0; l < lens[i]; l++) {
			fname1[l] = 'a';
			fname2[l] = 'A';
		}
		if (l > 0) {
			fname1[l] = '\0';
			fname2[l] = '\0';
			add_entry(a, fname1, NULL);
			add_entry(a, fname2, sym1);
			fcnt += 2;
		}
		if (l < 254) {
			fname1[l] = '.';
			fname1[l+1] = 'c';
			fname1[l+2] = '\0';
			fname2[l] = '.';
			fname2[l+1] = 'C';
			fname2[l+2] = '\0';
			add_entry(a, fname1, sym128);
			add_entry(a, fname2, sym255);
			fcnt += 2;
		}
		if (l < 252) {
			fname1[l] = '.';
			fname1[l+1] = 'p';
			fname1[l+2] = 'n';
			fname1[l+3] = 'g';
			fname1[l+4] = '\0';
			fname2[l] = '.';
			fname2[l+1] = 'P';
			fname2[l+2] = 'N';
			fname2[l+3] = 'G';
			fname2[l+4] = '\0';
			add_entry(a, fname1, NULL);
			add_entry(a, fname2, sym1);
			fcnt += 2;
		}
		if (l < 251) {
			fname1[l] = '.';
			fname1[l+1] = 'j';
			fname1[l+2] = 'p';
			fname1[l+3] = 'e';
			fname1[l+4] = 'g';
			fname1[l+5] = '\0';
			fname2[l] = '.';
			fname2[l+1] = 'J';
			fname2[l+2] = 'P';
			fname2[l+3] = 'E';
			fname2[l+4] = 'G';
			fname2[l+5] = '\0';
			add_entry(a, fname1, sym128);
			add_entry(a, fname2, sym255);
			fcnt += 2;
		}
	}

	/* Close out the archive. */
	assertEqualIntA(a, ARCHIVE_OK, archive_write_close(a));
	assertEqualIntA(a, ARCHIVE_OK, archive_write_free(a));

	return (fcnt);
}

DEFINE_TEST(test_write_format_iso9660_filename)
{
	unsigned char *buff;
	size_t buffsize = 120 * 2048;
	size_t used;
	int fcnt;
	struct fns fns;

	buff = malloc(buffsize);
	assert(buff != NULL);
	if (buff == NULL)
		return;
	memset(&fns, 0, sizeof(fns));

	/*
	 * Create ISO image with no option.
	 */
	fcnt = create_iso_image(buff, buffsize, &used, NULL);

	fns.names = malloc(sizeof(char *) * fcnt);
	assert(fns.names != NULL);
	if (fns.names == NULL) {
		free(buff);
		return;
	}
	fns.alloc = fcnt;

	/* Verify rockridge filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 255;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, ROCKRIDGE, &fns);

	/* Verify joliet filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 64;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, JOLIET, &fns);

	/* Verify ISO9660 filenames. */
	fns.longest_len = 0;
	fns.maxlen = 8+3+1;
	fns.maxflen = 8;
	fns.maxelen = 3;
	fns.opt = UPPER_CASE_ONLY | ONE_DOT;
	verify(buff, used, ISO9660, &fns);

	/*
	 * Create ISO image with iso-level=2.
	 */
	assertEqualInt(fcnt, create_iso_image(buff, buffsize, &used,
	    "iso-level=2"));

	/* Verify rockridge filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 255;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, ROCKRIDGE, &fns);

	/* Verify joliet filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 64;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, JOLIET, &fns);

	/* Verify ISO9660 filenames. */
	fns.longest_len = 0;
	fns.maxlen = 31;
	fns.maxflen = 30;
	fns.maxelen = 30;
	fns.opt = UPPER_CASE_ONLY | ONE_DOT;
	verify(buff, used, ISO9660, &fns);

	/*
	 * Create ISO image with iso-level=3.
	 */
	assertEqualInt(fcnt, create_iso_image(buff, buffsize, &used,
	    "iso-level=3"));

	/* Verify rockridge filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 255;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, ROCKRIDGE, &fns);

	/* Verify joliet filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 64;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, JOLIET, &fns);

	/* Verify ISO9660 filenames. */
	fns.longest_len = 0;
	fns.maxlen = 31;
	fns.maxflen = 30;
	fns.maxelen = 30;
	fns.opt = UPPER_CASE_ONLY | ONE_DOT;
	verify(buff, used, ISO9660, &fns);

	/*
	 * Create ISO image with iso-level=4.
	 */
	assertEqualInt(fcnt, create_iso_image(buff, buffsize, &used,
	    "iso-level=4"));

	/* Verify rockridge filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 255;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, ROCKRIDGE, &fns);

	/* Verify joliet filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 64;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, JOLIET, &fns);

	/* Verify ISO9660 filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 193;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, ISO9660, &fns);

	/*
	 * Create ISO image with iso-level=4 and !rockridge.
	 */
	assertEqualInt(fcnt, create_iso_image(buff, buffsize, &used,
	    "iso-level=4,!rockridge"));

	/* Verify joliet filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 64;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, JOLIET, &fns);

	/* Verify ISO9660 filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 207;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, ISO9660, &fns);

	/*
	 * Create ISO image with joliet=long.
	 */
	assertEqualInt(fcnt, create_iso_image(buff, buffsize, &used,
	    "joliet=long"));

	/* Verify rockridge filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 255;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, ROCKRIDGE, &fns);

	/* Verify joliet filenames. */
	fns.longest_len = 0;
	fns.maxlen = fns.maxflen = fns.maxelen = 103;
	fns.opt = ALLOW_LDOT;
	verify(buff, used, JOLIET, &fns);

	/* Verify ISO9660 filenames. */
	fns.longest_len = 0;
	fns.maxlen = 8+3+1;
	fns.maxflen = 8;
	fns.maxelen = 3;
	fns.opt = UPPER_CASE_ONLY | ONE_DOT;
	verify(buff, used, ISO9660, &fns);

	free(fns.names);
	free(buff);
}
