/*-
 * Copyright (c) 2003-2005 MAEKAWA Masahide <maekawa@cvsync.org>
 * 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.
 * 3. Neither the name of the author nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 <sys/types.h>
#include <sys/stat.h>

#include <stdio.h>

#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <string.h>

#include "compat_stdbool.h"
#include "compat_stdint.h"
#include "compat_inttypes.h"
#include "compat_limits.h"

#include "attribute.h"
#include "cvsync.h"
#include "cvsync_attr.h"
#include "filetypes.h"
#include "logmsg.h"

#include "defs.h"

#define	CVSUP_ATTR_FILETYPE	0x01
#define	CVSUP_ATTR_MTIME	0x02
#define	CVSUP_ATTR_SIZE		0x04
#define	CVSUP_ATTR_LINKTARGET	0x08
#define	CVSUP_ATTR_MODE		0x80

#define	CVSUP_ATTRS_DIR		(CVSUP_ATTR_MODE)
#define	CVSUP_ATTRS_FILE	(CVSUP_ATTR_MTIME|CVSUP_ATTR_SIZE|CVSUP_ATTR_MODE)
#define	CVSUP_ATTRS_RCS		(CVSUP_ATTR_MTIME|CVSUP_ATTR_MODE)
#define	CVSUP_ATTRS_SYMLINK	(CVSUP_ATTR_LINKTARGET)

bool cvsup_search_dir(struct cvsync_attr *, uint8_t *, uint8_t *);
bool cvsup_decode_dirattr(struct cvsync_attr *, uint8_t *, uint8_t *);

bool cvsup_decode_rcs(struct cvsync_attr *, uint8_t *, uint8_t *);
bool cvsup_decode_symlink(struct cvsync_attr *, uint8_t *, uint8_t *);

uint32_t cvsup_decode_attrs(uint8_t *, uint8_t *, uint8_t **);
uint8_t cvsup_decode_filetype(uint8_t *, uint8_t *, uint8_t **);
int64_t cvsup_decode_mtime(uint8_t *, uint8_t *, uint8_t **);
uint64_t cvsup_decode_size(uint8_t *, uint8_t *, uint8_t **);
size_t cvsup_decode_linktarget(uint8_t *, uint8_t *, uint8_t **, void *, size_t);
uint16_t cvsup_decode_mode(uint8_t *, uint8_t *, uint8_t **);
size_t cvsup_decode_length(uint8_t *, uint8_t *);
uint8_t *cvsup_ignore_bit(uint8_t *, uint8_t *);

uint8_t *
cvsup_examine(uint8_t *sp, uint8_t *bp)
{
	uint8_t *ep;

	if (*sp++ != 'F') {
		logmsg_err("This file seems not be CVSup scan file");
		return (NULL);
	}
	if ((ep = memchr(sp, '\n', (size_t)(bp - sp))) == NULL) {
		logmsg_err("premature EOF");
		return (NULL);
	}
	if (ep - sp < 4) {
		logmsg_err("premature EOF");
		return (NULL);
	}
	if (*sp++ != ' ') {
		logmsg_err("no separators");
		return (NULL);
	}
	if (*sp != '5') {
		logmsg_err("%c: invalid version (!= 5)", *sp);
		return (NULL);
	}
	if (*++sp != ' ') {
		logmsg_err("no separators");
		return (NULL);
	}

	return (ep + 1);
}

bool
cvsup_decode_dir(struct cvsync_attr *attr, uint8_t *sp, uint8_t *bp)
{
	if (!cvsup_search_dir(attr, sp, bp))
		return (false);

	attr->ca_tag = FILETYPE_DIR;
	attr->ca_auxlen = attr_rcs_encode_dir(attr->ca_aux,
					      sizeof(attr->ca_aux),
					      attr->ca_mode);
	if (attr->ca_auxlen == 0) {
		logmsg_err("%s", strerror(ENOBUFS));
		return (false);
	}

	logmsg_verbose("%.*s %o", attr->ca_namelen, attr->ca_name,
		       attr->ca_mode);

	return (true);
}

bool
cvsup_search_dir(struct cvsync_attr *attr, uint8_t *sp, uint8_t *bp)
{
	uint8_t *ep, tag;
	int n = 1;

	while (sp < bp) {
		if (bp - sp < 3) {
			logmsg_err("premature EOF");
			return (false);
		}
		tag = *sp++;
		if (*sp++ != ' ') {
			logmsg_err("no separators");
			return (false);
		}

		if ((ep = memchr(sp, '\n', (size_t)(bp - sp))) == NULL) {
			logmsg_err("premature EOF");
			return (false);
		}

		switch (tag) {
		case 'D':
			/* skip in this context. */
			n++;
			break;
		case 'U':
			if (--n < 0) {
				logmsg_err("Found extra 'U's");
				return (false);
			}
			if (n > 0) {
				/* skip in this context. */
				break;
			}
			if ((size_t)(ep - sp) <= attr->ca_namelen) {
				logmsg_err("Not found 'U' for %.*s",
					   attr->ca_namelen, attr->ca_name);
				return (false);
			}
			if (memcmp(sp, attr->ca_name,
				   attr->ca_namelen) != 0) {
				logmsg_err("Not found 'U' for %.*s",
					   attr->ca_namelen, attr->ca_name);
				return (false);
			}
			if (sp[attr->ca_namelen] != ' ') {
				logmsg_err("no separators");
				return (false);
			}
			sp += attr->ca_namelen + 1;

			if (!cvsup_decode_dirattr(attr, sp, ep))
				return (false);

			return (true);
		case 'V':
		case 'v':
			/* Nothing to do. */
			break;
		default:
			logmsg_err("%c: unknown entry tag", tag);
			return (false);
		}

		sp = ep + 1;
	}

	logmsg_err("Not found 'U' for %.*s", attr->ca_namelen,
		   attr->ca_name);

	return (false);
}

bool
cvsup_decode_dirattr(struct cvsync_attr *attr, uint8_t *sp, uint8_t *ep)
{
	uint32_t attrs;
	uint8_t type;
	int i;

	if ((attrs = cvsup_decode_attrs(sp, ep, &sp)) == (uint32_t)-1)
		return (false);
	if ((attrs & CVSUP_ATTR_FILETYPE) != CVSUP_ATTR_FILETYPE) {
		logmsg_err("Not found FileType field");
		return (false);
	}
	if ((type = cvsup_decode_filetype(sp, ep, &sp)) == (uint8_t)-1)
		return (false);
	if (type != FILETYPE_DIR) {
		logmsg_err("%c: invalid FileType", type);
		return (false);
	}
	if ((attrs & CVSUP_ATTRS_DIR) != CVSUP_ATTRS_DIR) {
		logmsg_err("Not enough fields");
		return (false);
	}
	for (i = 0 ; i < 6 ; i++) {
		if (attrs & 1) {
			if ((sp = cvsup_ignore_bit(sp, ep)) == NULL)
				return (false);
		}
		attrs >>= 1;
	}
	if ((attr->ca_mode = cvsup_decode_mode(sp, ep, &sp)) == (uint16_t)-1)
		return (false);

	return (true);
}

bool
cvsup_decode_file(struct cvsync_attr *attr, uint8_t *sp, uint8_t *ep)
{
	uint32_t attrs;
	uint8_t *sv_sp = sp, type;
	int i;

	if ((attrs = cvsup_decode_attrs(sp, ep, &sp)) == (uint32_t)-1)
		return (false);
	if ((attrs & CVSUP_ATTR_FILETYPE) != CVSUP_ATTR_FILETYPE) {
		logmsg_err("Not found FileType field");
		return (false);
	}
	if ((type = cvsup_decode_filetype(sp, ep, &sp)) == (uint8_t)-1)
		return (false);
	switch (type) {
	case FILETYPE_FILE:
		if (IS_FILE_RCS(attr->ca_name, attr->ca_namelen))
			return (cvsup_decode_rcs(attr, sv_sp, ep));
		break;
	case FILETYPE_SYMLINK:
		return (cvsup_decode_symlink(attr, sv_sp, ep));
	default:
		logmsg_err("%c: invalid FileType", type);
		return (false);
	}
	if ((attrs & CVSUP_ATTRS_FILE) != CVSUP_ATTRS_FILE) {
		logmsg_err("Not enough fields");
		return (false);
	}
	if ((attr->ca_mtime = cvsup_decode_mtime(sp, ep, &sp)) == (int64_t)-1)
		return (false);
	if ((attr->ca_size = cvsup_decode_size(sp, ep, &sp)) == (uint64_t)-1)
		return (false);
	attrs >>= 4;
	for (i = 0 ; i < 3 ; i++) {
		if (attrs & 1) {
			if ((sp = cvsup_ignore_bit(sp, ep)) == NULL)
				return (false);
		}
		attrs >>= 1;
	}
	if ((attr->ca_mode = cvsup_decode_mode(sp, ep, &sp)) == (uint16_t)-1)
		return (false);

	attr->ca_tag = FILETYPE_FILE;
	attr->ca_auxlen = attr_rcs_encode_file(attr->ca_aux,
					       sizeof(attr->ca_aux),
					       (time_t)attr->ca_mtime,
					       (off_t)attr->ca_size,
					       attr->ca_mode);
	if (attr->ca_auxlen == 0) {
		logmsg_err("%s", strerror(ENOBUFS));
		return (false);
	}

	logmsg_verbose("%.*s %" PRId64 " %" PRIu64 " %o", attr->ca_namelen,
		       attr->ca_name, attr->ca_mtime, attr->ca_size,
		       attr->ca_mode);

	return (true);
}

bool
cvsup_decode_rcs(struct cvsync_attr *attr, uint8_t *sp, uint8_t *ep)
{
	uint32_t attrs;
	uint8_t type;
	int i;

	if ((attrs = cvsup_decode_attrs(sp, ep, &sp)) == (uint32_t)-1)
		return (false);
	if ((attrs & CVSUP_ATTR_FILETYPE) != CVSUP_ATTR_FILETYPE) {
		logmsg_err("Not found FileType field");
		return (false);
	}
	if ((type = cvsup_decode_filetype(sp, ep, &sp)) == (uint8_t)-1)
		return (false);
	if (type != FILETYPE_FILE) {
		logmsg_err("%c: invalid FileType", type);
		return (false);
	}
	if ((attrs & CVSUP_ATTRS_RCS) != CVSUP_ATTRS_RCS) {
		logmsg_err("Not enough fields");
		return (false);
	}
	if ((attr->ca_mtime = cvsup_decode_mtime(sp, ep, &sp)) == (int64_t)-1)
		return (false);
	attrs >>= 2;
	for (i = 0 ; i < 5 ; i++) {
		if (attrs & 1) {
			if ((sp = cvsup_ignore_bit(sp, ep)) == NULL)
				return (false);
		}
		attrs >>= 1;
	}
	if ((attr->ca_mode = cvsup_decode_mode(sp, ep, &sp)) == (uint16_t)-1)
		return (false);

	attr->ca_tag = FILETYPE_RCS;
	attr->ca_auxlen = attr_rcs_encode_rcs(attr->ca_aux,
					      sizeof(attr->ca_aux),
					      (time_t)attr->ca_mtime,
					      attr->ca_mode);
	if (attr->ca_auxlen == 0) {
		logmsg_err("%s", strerror(ENOBUFS));
		return (false);
	}

	logmsg_verbose("%.*s %" PRId64 " %o", attr->ca_namelen, attr->ca_name,
		       attr->ca_mtime, attr->ca_mode);

	return (true);
}

bool
cvsup_decode_symlink(struct cvsync_attr *attr, uint8_t *sp, uint8_t *ep)
{
	uint32_t attrs;
	uint8_t type;

	if ((attrs = cvsup_decode_attrs(sp, ep, &sp)) == (uint32_t)-1)
		return (false);
	if ((attrs & CVSUP_ATTR_FILETYPE) != CVSUP_ATTR_FILETYPE) {
		logmsg_err("Not found FileType field");
		return (false);
	}
	if ((type = cvsup_decode_filetype(sp, ep, &sp)) == (uint8_t)-1)
		return (false);
	if (type != FILETYPE_SYMLINK) {
		logmsg_err("%c: invalid FileType", type);
		return (false);
	}
	if ((attrs & CVSUP_ATTRS_SYMLINK) != CVSUP_ATTRS_SYMLINK) {
		logmsg_err("Not enough fields");
		return (false);
	}
	attrs >>= 1;
	attr->ca_auxlen = cvsup_decode_linktarget(sp, ep, &sp, attr->ca_aux,
						  sizeof(attr->ca_aux));
	if (attr->ca_auxlen == 0)
		return (false);

	attr->ca_tag = FILETYPE_SYMLINK;

	logmsg_verbose("%.*s -> %.*s", attr->ca_namelen, attr->ca_name,
		       attr->ca_auxlen, attr->ca_aux);

	return (true);
}

uint32_t
cvsup_decode_attrs(uint8_t *sp, uint8_t *ep, uint8_t **new_sp)
{
	uint32_t attrs = 0;
	uint8_t *sep;
	size_t n;

	if ((sep = memchr(sp, '#', (size_t)(ep - sp))) == NULL) {
		logmsg_err("no separators");
		return ((uint32_t)-1);
	}
	if ((n = cvsup_decode_length(sp, sep)) == (size_t)-1)
		return ((uint32_t)-1);
	if (((sp = sep + 1) >= ep) || ((size_t)(ep - sp) < n)) {
		logmsg_err("premature EOF");
		return ((uint32_t)-1);
	}
	if ((n == 0) || (n > sizeof(attrs) * 2)) {
		logmsg_err("%d: invalid Attrs length", n);
		return ((uint32_t)-1);
	}

	*new_sp = sp + n;

	while (n-- > 0) {
		if (!isxdigit((int)(*sp))) {
			logmsg_err("%c: invalid Attrs specifier", *sp);
			return ((uint32_t)-1);
		}
		attrs <<= 4;
		if (isdigit((int)(*sp)))
			attrs += *sp - '0';
		else
			attrs += tolower(*sp) - 'a' + 10;
		sp++;
	}

	return (attrs);
}

uint8_t
cvsup_decode_filetype(uint8_t *sp, uint8_t *ep, uint8_t **new_sp)
{
	uint8_t tag, *sep;
	size_t n, i;
	int type = 0, c;

	if ((sep = memchr(sp, '#', (size_t)(ep - sp))) == NULL) {
		logmsg_err("no separators");
		return ((uint8_t)-1);
	}
	if ((n = cvsup_decode_length(sp, sep)) == (size_t)-1)
		return ((uint8_t)-1);
	if (((sp = sep + 1) >= ep) || ((size_t)(ep - sp) < n)) {
		logmsg_err("premature EOF");
		return ((uint8_t)-1);
	}

	for (i = 0 ; i < n ; i++) {
		if (!isdigit((int)(*sp))) {
			logmsg_err("%c: invalid FileType specifier", *sp);
			return ((uint8_t)-1);
		}
		c = (int)(*sp++ - '0');
		if (INT_MAX / 10 < type) {
			logmsg_err("%.*s: %s", n, sep + 1, strerror(ERANGE));
			return ((uint8_t)-1);
		}
		type *= 10;
		if (INT_MAX - type < c) {
			logmsg_err("%.*s: %s", n, sep + 1, strerror(ERANGE));
			return ((uint8_t)-1);
		}
		type += c;
	}

	*new_sp = sp;

	switch (type) {
	case 0:
		logmsg_err("%d: unsupported FileType", type);
		return ((uint8_t)-1);
	case 1:
		tag = FILETYPE_FILE;
		break;
	case 2:
		tag = FILETYPE_DIR;
		break;
	case 3:
		tag = FILETYPE_CHRDEV;
		break;
	case 4:
		tag = FILETYPE_BLKDEV;
		break;
	case 5:
		tag = FILETYPE_SYMLINK;
		break;
	default:
		logmsg_err("%d: unsupported FileType", type);
		return ((uint8_t)-1);
	}

	return (tag);
}

int64_t
cvsup_decode_mtime(uint8_t *sp, uint8_t *ep, uint8_t **new_sp)
{
	int64_t mtime = 0, c;
	uint8_t *sep;
	size_t n, i;

	if ((sep = memchr(sp, '#', (size_t)(ep - sp))) == NULL) {
		logmsg_err("no separators");
		return (-1);
	}
	if ((n = cvsup_decode_length(sp, sep)) == (size_t)-1)
		return (-1);
	if (((sp = sep + 1) >= ep) || ((size_t)(ep - sp) < n)) {
		logmsg_err("premature EOF");
		return (-1);
	}

	for (i = 0 ; i < n ; i++) {
		if (!isdigit((int)(*sp))) {
			logmsg_err("%c: invalid ModTime specifier", *sp);
			return (-1);
		}
		c = (int64_t)(*sp++ - '0');
		if (INT64_MAX / 10 < mtime) {
			logmsg_err("%.*s: %s", n, sep + 1, strerror(ERANGE));
			return (-1);
		}
		mtime *= 10;
		if (INT64_MAX - mtime < c) {
			logmsg_err("%.*s: %s", n, sep + 1, strerror(ERANGE));
			return (-1);
		}
		mtime += c;
	}

	*new_sp = sp;

	return (mtime);
}

uint64_t
cvsup_decode_size(uint8_t *sp, uint8_t *ep, uint8_t **new_sp)
{
	uint64_t size = 0, c;
	uint8_t *sep;
	size_t n, i;

	if ((sep = memchr(sp, '#', (size_t)(ep - sp))) == NULL) {
		logmsg_err("no separators");
		return ((uint64_t)-1);
	}
	if ((n = cvsup_decode_length(sp, sep)) == (size_t)-1)
		return ((uint64_t)-1);
	if (((sp = sep + 1) >= ep) || ((size_t)(ep - sp) < n)) {
		logmsg_err("premature EOF");
		return ((uint64_t)-1);
	}

	for (i = 0 ; i < n ; i++) {
		if (!isdigit((int)(*sp))) {
			logmsg_err("%c: invalid ModTime specifier", *sp);
			return ((uint64_t)-1);
		}
		c = (uint64_t)(*sp++ - '0');
		if (UINT64_MAX / 10 < size) {
			logmsg_err("%.*s: %s", n, sep + 1, strerror(ERANGE));
			return ((uint64_t)-1);
		}
		size *= 10;
		if (UINT64_MAX - size < c) {
			logmsg_err("%.*s: %s", n, sep + 1, strerror(ERANGE));
			return ((uint64_t)-1);
		}
		size += c;
	}

	*new_sp = sp;

	return (size);
}

size_t
cvsup_decode_linktarget(uint8_t *sp, uint8_t *ep, uint8_t **new_sp,
			void *buffer, size_t bufsize)
{
	uint8_t *linksp = buffer, *linkbp = linksp + bufsize, *sep;
	size_t n;

	if ((sep = memchr(sp, '#', (size_t)(ep - sp))) == NULL) {
		logmsg_err("no separators");
		return (0);
	}
	if ((n = cvsup_decode_length(sp, sep)) == (size_t)-1)
		return (0);
	if (((sp = sep + 1) >= ep) || ((size_t)(ep - sp) < n)) {
		logmsg_err("premature EOF");
		return (0);
	}
	if ((n == 0) || (n > bufsize)) {
		logmsg_err("%d: invalid LinkTarget length", n);
		return (0);
	}

	while (n-- > 0) {
		if (linksp == linkbp)
			return (0);
		if (*sp != '\\') {
			*linksp++ = *sp++;
			continue;
		}
		sp++;
		n--;
		if (ep - sp < 1) {
			logmsg_err("premature EOF");
			return (0);
		}
		switch (*sp) {
		case '_':
			*linksp++ = ' ';
			break;
		default:
			*linksp++ = '\\';
			*linksp++ = *sp++;
		}
	}

	*new_sp = sp;

	return (linksp - (uint8_t *)buffer);
}

uint16_t
cvsup_decode_mode(uint8_t *sp, uint8_t *ep, uint8_t **new_sp)
{
	uint16_t mode = 0;
	uint8_t *sep;
	size_t n;

	if ((sep = memchr(sp, '#', (size_t)(ep - sp))) == NULL) {
		logmsg_err("no separators");
		return ((uint16_t)-1);
	}
	if ((n = cvsup_decode_length(sp, sep)) == (size_t)-1)
		return ((uint16_t)-1);
	if (((sp = sep + 1) >= ep) || ((size_t)(ep - sp) < n)) {
		logmsg_err("premature EOF");
		return ((uint16_t)-1);
	}
	if ((n == 0) || (n > sizeof(mode) * 2)) {
		logmsg_err("%d: invalid Mode length", n);
		return ((uint16_t)-1);
	}

	while (n-- > 0) {
		if ((*sp < '0') || (*sp > '7')) {
			logmsg_err("%c: invalid Mode specifier", *sp);
			return ((uint16_t)-1);
		}
		mode = (mode << 3) + *sp++ - '0';
	}

	*new_sp = sp;

	return (mode & mode_umask);
}

size_t
cvsup_decode_length(uint8_t *sp, uint8_t *ep)
{
	uint8_t *sv_sp = sp;
	size_t n = 0, c;

	while (sp < ep) {
		if (!isdigit((int)(*sp))) {
			logmsg_err("%c: invalid length specifier", *sp);
			return ((size_t)-1);
		}
		c = (size_t)(*sp++ - '0');
		if (SIZE_MAX / 10 < n) {
			logmsg_err("%.*s: %s", ep - sv_sp, sv_sp,
				   strerror(ERANGE));
			return ((size_t)-1);
		}
		n *= 10;
		if (SIZE_MAX - n < c) {
			logmsg_err("%.*s: %s", ep - sv_sp, sv_sp,
				   strerror(ERANGE));
			return ((size_t)-1);
		}
		n += c;
	}

	return (n);
}

uint8_t *
cvsup_ignore_bit(uint8_t *sp, uint8_t *ep)
{
	uint8_t *sep;
	size_t n;

	if ((sep = memchr(sp, '#', (size_t)(ep - sp))) == NULL) {
		logmsg_err("no separators");
		return (NULL);
	}
	if ((n = cvsup_decode_length(sp, sep)) == (size_t)-1)
		return (NULL);
	if (((sp = sep + 1) >= ep) || ((size_t)(ep - sp) < n)) {
		logmsg_err("premature EOF");
		return (NULL);
	}

	return (sep + n + 1);
}


syntax highlighted by Code2HTML, v. 0.9.1