/*
   Unix SMB/CIFS implementation.
   filename matching routine
   Copyright (C) Andrew Tridgell 1992-2004

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
   This module was originally based on fnmatch.c copyright by the Free
   Software Foundation. It bears little (if any) resemblance to that
   code now
*/

/**
 * @file
 * @brief MS-style Filename matching
 */

#include "replace.h"
#include "lib/util/samba_util.h"
#include "libcli/smb/smb_constants.h"

static int null_match(const char *p)
{
	for (;*p;p++) {
		if (*p != '*' &&
		    *p != '<' &&
		    *p != '"' &&
		    *p != '>') return -1;
	}
	return 0;
}

/*
  the max_n structure is purely for efficiency, it doesn't contribute
  to the matching algorithm except by ensuring that the algorithm does
  not grow exponentially
*/
struct max_n {
	const char *predot;
	const char *postdot;
};


/*
  p and n are the pattern and string being matched. The max_n array is
  an optimisation only. The ldot pointer is NULL if the string does
  not contain a '.', otherwise it points at the last dot in 'n'.
*/
static int ms_fnmatch_core(const char *p, const char *n,
			   struct max_n *max_n, const char *ldot,
			   bool is_case_sensitive)
{
	codepoint_t c, c2;
	int i;
	size_t size, size_n;

	while ((c = next_codepoint(p, &size))) {
		p += size;

		switch (c) {
		case '*':
			/* a '*' matches zero or more characters of any type */
			if (max_n != NULL && max_n->predot &&
			    max_n->predot <= n) {
				return null_match(p);
			}
			for (i=0; n[i]; i += size_n) {
				next_codepoint(n+i, &size_n);
				if (ms_fnmatch_core(p, n+i, max_n+1, ldot, is_case_sensitive) == 0) {
					return 0;
				}
			}
			if (max_n != NULL && (!max_n->predot ||
			    max_n->predot > n)) {
				max_n->predot = n;
			}
			return null_match(p);

		case '<':
			/* a '<' matches zero or more characters of
			   any type, but stops matching at the last
			   '.' in the string. */
			if (max_n != NULL && max_n->predot &&
			    max_n->predot <= n) {
				return null_match(p);
			}
			if (max_n != NULL && max_n->postdot &&
			    max_n->postdot <= n && n <= ldot) {
				return -1;
			}
			for (i=0; n[i]; i += size_n) {
				next_codepoint(n+i, &size_n);
				if (ms_fnmatch_core(p, n+i, max_n+1, ldot, is_case_sensitive) == 0) return 0;
				if (n+i == ldot) {
					if (ms_fnmatch_core(p, n+i+size_n, max_n+1, ldot, is_case_sensitive) == 0) return 0;
					if (max_n != NULL) {
						if (!max_n->postdot ||
						    max_n->postdot > n) {
							max_n->postdot = n;
						}
					}
					return -1;
				}
			}
			if (max_n != NULL && (!max_n->predot ||
			    max_n->predot > n)) {
				max_n->predot = n;
			}
			return null_match(p);

		case '?':
			/* a '?' matches any single character */
			if (! *n) {
				return -1;
			}
			next_codepoint(n, &size_n);
			n += size_n;
			break;

		case '>':
			/* a '?' matches any single character, but
			   treats '.' specially */
			if (n[0] == '.') {
				if (! n[1] && null_match(p) == 0) {
					return 0;
				}
				break;
			}
			if (! *n) return null_match(p);
			next_codepoint(n, &size_n);
			n += size_n;
			break;

		case '"':
			/* a bit like a soft '.' */
			if (*n == 0 && null_match(p) == 0) {
				return 0;
			}
			if (*n != '.') return -1;
			next_codepoint(n, &size_n);
			n += size_n;
			break;

		default:
			c2 = next_codepoint(n, &size_n);
			if (c != c2) {
				if (is_case_sensitive) {
					return -1;
				}
				if (codepoint_cmpi(c, c2) != 0) {
					return -1;
				}
			}
			n += size_n;
			break;
		}
	}

	if (! *n) {
		return 0;
	}

	return -1;
}

int ms_fnmatch_protocol(const char *pattern, const char *string, int protocol,
			bool is_case_sensitive)
{
	int ret = -1;
	size_t count, i;

	if (strcmp(string, "..") == 0) {
		string = ".";
	}

	if (strpbrk(pattern, "<>*?\"") == NULL) {
		/* this is not just an optimisation - it is essential
		   for LANMAN1 correctness */
		return strcasecmp_m(pattern, string);
	}

	if (protocol <= PROTOCOL_LANMAN2) {
		char *p = talloc_strdup(NULL, pattern);
		if (p == NULL) {
			return -1;
		}
		/*
		  for older negotiated protocols it is possible to
		  translate the pattern to produce a "new style"
		  pattern that exactly matches w2k behaviour
		*/
		for (i=0;p[i];i++) {
			if (p[i] == '?') {
				p[i] = '>';
			} else if (p[i] == '.' &&
				   (p[i+1] == '?' ||
				    p[i+1] == '*' ||
				    p[i+1] == 0)) {
				p[i] = '"';
			} else if (p[i] == '*' &&
				   p[i+1] == '.') {
				p[i] = '<';
			}
		}
		ret = ms_fnmatch_protocol(p, string, PROTOCOL_NT1,
					  is_case_sensitive);
		talloc_free(p);
		return ret;
	}

	for (count=i=0;pattern[i];i++) {
		if (pattern[i] == '*' || pattern[i] == '<') count++;
	}

	/* If the pattern includes '*' or '<' */
	if (count >= 1) {
		struct max_n max_n[count];

		memset(max_n, 0, sizeof(struct max_n) * count);

		ret = ms_fnmatch_core(pattern, string, max_n, strrchr(string, '.'),
				      is_case_sensitive);
	} else {
		ret = ms_fnmatch_core(pattern, string, NULL, strrchr(string, '.'),
				      is_case_sensitive);
	}

	return ret;
}


/** a generic fnmatch function - uses for non-CIFS pattern matching */
int gen_fnmatch(const char *pattern, const char *string)
{
	return ms_fnmatch_protocol(pattern, string, PROTOCOL_NT1, false);
}
