/*
 * Copyright (c) 2004-2016 Apple Computer, Inc. All rights reserved.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
 *
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. The rights granted to you under the License
 * may not be used to create, or enable the creation or redistribution of,
 * unlawful or unlicensed copies of an Apple operating system, or to
 * circumvent, violate, or enable the circumvention or violation of, any
 * terms of an Apple operating system software license agreement.
 *
 * Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this file.
 *
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 *
 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
 */
/*-
 * Copyright (c) 1990, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Chris Torek.
 *
 * 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. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *	This product includes software developed by the University of
 *	California, Berkeley and its contributors.
 * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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 <stdarg.h>
#include <stddef.h>
#include <string.h>
#include <sys/cdefs.h>
#include <sys/param.h>

quad_t strtoq(const char *, char **, int);
u_quad_t strtouq(const char *, char **, int);

static inline int
isspace(char c)
{
	return c == ' ' || c == '\t' || c == '\n' || c == '\12';
}

#define BUF             32      /* Maximum length of numeric string. */

/*
 * Flags used during conversion.
 */
#define LONG            0x01    /* l: long or double */
#define SHORT           0x04    /* h: short */
#define SUPPRESS        0x08    /* *: suppress assignment */
#define POINTER         0x10    /* p: void * (as hex) */
#define NOSKIP          0x20    /* [ or c: do not skip blanks */
#define LONGLONG        0x400   /* ll: long long (+ deprecated q: quad) */
#define SHORTSHORT      0x4000  /* hh: char */
#define UNSIGNED        0x8000  /* %[oupxX] conversions */

/*
 * The following are used in numeric conversions only:
 * SIGNOK, NDIGITS, DPTOK, and EXPOK are for floating point;
 * SIGNOK, NDIGITS, PFXOK, and NZDIGITS are for integral.
 */
#define SIGNOK          0x40    /* +/- is (still) legal */
#define NDIGITS         0x80    /* no digits detected */

#define DPTOK           0x100   /* (float) decimal point is still legal */
#define EXPOK           0x200   /* (float) exponent (e+3, etc) still legal */

#define PFXOK           0x100   /* 0x prefix is (still) legal */
#define NZDIGITS        0x200   /* no zero digits detected */

/*
 * Conversion types.
 */
#define CT_CHAR         0       /* %c conversion */
#define CT_CCL          1       /* %[...] conversion */
#define CT_STRING       2       /* %s conversion */
#define CT_INT          3       /* %[dioupxX] conversion */

static const u_char *__sccl(char *, const u_char *);

int sscanf(const char *, const char *, ...);
int vsscanf(const char *, char const *, va_list);

int
sscanf(const char *ibuf, const char *fmt, ...)
{
	va_list ap;
	int ret;

	va_start(ap, fmt);
	ret = vsscanf(ibuf, fmt, ap);
	va_end(ap);
	return ret;
}

int
vsscanf(const char *inp, char const *fmt0, va_list ap)
{
	ssize_t inr;
	const u_char *fmt = (const u_char *)fmt0;
	ssize_t width;           /* field width, or 0 */
	char *p;                /* points into all kinds of strings */
	int flags;              /* flags as defined above */
	char *p0;               /* saves original value of p when necessary */
	int nassigned = 0;          /* number of fields assigned */
	int nconversions = 0;       /* number of conversions */
	int nread = 0;              /* number of characters consumed from fp */
	int base = 0;               /* base argument to conversion function */
	char ccltab[256];       /* character class table for %[...] */
	char buf[BUF];          /* buffer for numeric conversions */

	/* `basefix' is used to avoid `if' tests in the integer scanner */
	static short basefix[17] =
	{ 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };

	inr = (ssize_t)strlen(inp);

	for (;;) {
		char c = (char)*fmt++; /* character from format, or conversion */
		if (c == 0) {
			return nassigned;
		}
		if (isspace(c)) {
			while (inr > 0 && isspace(*inp)) {
				nread++;
				inr--;
				inp++;
			}
			continue;
		}
		if (c != '%') {
			goto literal;
		}
		width = 0;
		flags = 0;
		/*
		 * switch on the format.  continue if done;
		 * break once format type is derived.
		 */
again:
		c = (char)*fmt++;
		switch (c) {
		case '%':
literal:
			if (inr <= 0) {
				goto input_failure;
			}
			if (*inp != c) {
				goto match_failure;
			}
			inr--;
			inp++;
			nread++;
			continue;

		case '*':
			flags |= SUPPRESS;
			goto again;
		case 'l':
			if (flags & LONG) {
				flags &= ~LONG;
				flags |= LONGLONG;
			} else {
				flags |= LONG;
			}
			goto again;
		case 'q':
			flags |= LONGLONG;      /* not quite */
			goto again;
		case 'h':
			if (flags & SHORT) {
				flags &= ~SHORT;
				flags |= SHORTSHORT;
			} else {
				flags |= SHORT;
			}
			goto again;

		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			width = width * 10 + c - '0';
			goto again;

		/*
		 * Conversions.
		 */
		case 'd':
			c = CT_INT;
			base = 10;
			break;

		case 'i':
			c = CT_INT;
			base = 0;
			break;

		case 'o':
			c = CT_INT;
			flags |= UNSIGNED;
			base = 8;
			break;

		case 'u':
			c = CT_INT;
			flags |= UNSIGNED;
			base = 10;
			break;

		case 'X':
		case 'x':
			flags |= PFXOK; /* enable 0x prefixing */
			c = CT_INT;
			flags |= UNSIGNED;
			base = 16;
			break;

		case 's':
			c = CT_STRING;
			break;

		case '[':
			fmt = __sccl(ccltab, fmt);
			flags |= NOSKIP;
			c = CT_CCL;
			break;

		case 'c':
			flags |= NOSKIP;
			c = CT_CHAR;
			break;

		case 'p':       /* pointer format is like hex */
			flags |= POINTER | PFXOK;
			c = CT_INT;
			flags |= UNSIGNED;
			base = 16;
			break;

		case 'n':
			nconversions++;
			if (flags & SUPPRESS) { /* ??? */
				continue;
			}
			if (flags & SHORTSHORT) {
				*va_arg(ap, char *) = (char)nread;
			} else if (flags & SHORT) {
				*va_arg(ap, short *) = (short)nread;
			} else if (flags & LONG) {
				*va_arg(ap, long *) = (long)nread;
			} else if (flags & LONGLONG) {
				*va_arg(ap, long long *) = (long long)nread;
			} else {
				*va_arg(ap, int *) = (int)nread;
			}
			continue;
		}

		/*
		 * We have a conversion that requires input.
		 */
		if (inr <= 0) {
			goto input_failure;
		}

		/*
		 * Consume leading white space, except for formats
		 * that suppress this.
		 */
		if ((flags & NOSKIP) == 0) {
			while (isspace(*inp)) {
				nread++;
				if (--inr > 0) {
					inp++;
				} else {
					goto input_failure;
				}
			}
			/*
			 * Note that there is at least one character in
			 * the buffer, so conversions that do not set NOSKIP
			 * can no longer result in an input failure.
			 */
		}

		/*
		 * Do the conversion.
		 */
		switch (c) {
		case CT_CHAR:
			/* scan arbitrary characters (sets NOSKIP) */
			if (width == 0) {
				width = 1;
			}
			if (flags & SUPPRESS) {
				size_t sum = 0;
				for (;;) {
					ssize_t n = inr;
					if (n < width) {
						sum += (size_t)n;
						width -= n;
						inp += n;
						if (sum == 0) {
							goto input_failure;
						}
						break;
					} else {
						sum += (size_t)width;
						inr -= width;
						inp += width;
						break;
					}
				}
				nread += sum;
			} else {
				bcopy(inp, va_arg(ap, char *), width);
				inr -= width;
				inp += width;
				nread += width;
				nassigned++;
			}
			nconversions++;
			break;

		case CT_CCL: {
			/* scan a (nonempty) character class (sets NOSKIP) */
			if (width == 0) {
				width = SSIZE_MAX;     /* `infinity' */
			}
			/* take only those things in the class */
			ptrdiff_t n;
			if (flags & SUPPRESS) {
				n = 0;
				while (ccltab[(unsigned char)*inp]) {
					n++;
					inr--;
					inp++;
					if (--width == 0) {
						break;
					}
					if (inr <= 0) {
						if (n == 0) {
							goto input_failure;
						}
						break;
					}
				}
				if (n == 0) {
					goto match_failure;
				}
			} else {
				p0 = p = va_arg(ap, char *);
				while (ccltab[(unsigned char)*inp]) {
					inr--;
					*p++ = *inp++;
					if (--width == 0) {
						break;
					}
					if (inr <= 0) {
						if (p == p0) {
							goto input_failure;
						}
						break;
					}
				}
				n = p - p0;
				if (n == 0) {
					goto match_failure;
				}
				*p = 0;
				nassigned++;
			}
			nread += n;
			nconversions++;
			break;
		}

		case CT_STRING:
			/* like CCL, but zero-length string OK, & no NOSKIP */
			if (width == 0) {
				width = SSIZE_MAX;
			}
			if (flags & SUPPRESS) {
				size_t n = 0;
				while (!isspace(*inp)) {
					n++;
					inr--;
					inp++;
					if (--width == 0) {
						break;
					}
					if (inr <= 0) {
						break;
					}
				}
				nread += n;
			} else {
				p0 = p = va_arg(ap, char *);
				while (!isspace(*inp)) {
					inr--;
					*p++ = *inp++;
					if (--width == 0) {
						break;
					}
					if (inr <= 0) {
						break;
					}
				}
				*p = 0;
				nread += p - p0;
				nassigned++;
			}
			nconversions++;
			continue;

		case CT_INT:
			/* scan an integer as if by the conversion function */
			if (width <= 0 || width > (ssize_t)(sizeof(buf) - 1)) {
				width = sizeof(buf) - 1;
			}
			flags |= SIGNOK | NDIGITS | NZDIGITS;
			for (p = buf; width; width--) {
				c = *inp;
				/*
				 * Switch on the character; `goto ok'
				 * if we accept it as a part of number.
				 */
				switch (c) {
				/*
				 * The digit 0 is always legal, but is
				 * special.  For %i conversions, if no
				 * digits (zero or nonzero) have been
				 * scanned (only signs), we will have
				 * base==0.  In that case, we should set
				 * it to 8 and enable 0x prefixing.
				 * Also, if we have not scanned zero digits
				 * before this, do not turn off prefixing
				 * (someone else will turn it off if we
				 * have scanned any nonzero digits).
				 */
				case '0':
					if (base == 0) {
						base = 8;
						flags |= PFXOK;
					}
					if (flags & NZDIGITS) {
						flags &= ~(SIGNOK | NZDIGITS | NDIGITS);
					} else {
						flags &= ~(SIGNOK | PFXOK | NDIGITS);
					}
					goto ok;

				/* 1 through 7 always legal */
				case '1': case '2': case '3':
				case '4': case '5': case '6': case '7':
					base = basefix[base];
					flags &= ~(SIGNOK | PFXOK | NDIGITS);
					goto ok;

				/* digits 8 and 9 ok iff decimal or hex */
				case '8': case '9':
					base = basefix[base];
					if (base <= 8) {
						break;  /* not legal here */
					}
					flags &= ~(SIGNOK | PFXOK | NDIGITS);
					goto ok;

				/* letters ok iff hex */
				case 'A': case 'B': case 'C':
				case 'D': case 'E': case 'F':
				case 'a': case 'b': case 'c':
				case 'd': case 'e': case 'f':
					/* no need to fix base here */
					if (base <= 10) {
						break;  /* not legal here */
					}
					flags &= ~(SIGNOK | PFXOK | NDIGITS);
					goto ok;

				/* sign ok only as first character */
				case '+': case '-':
					if (flags & SIGNOK) {
						flags &= ~SIGNOK;
						goto ok;
					}
					break;

				/* x ok iff flag still set & 2nd char */
				case 'x': case 'X':
					if (flags & PFXOK && p == buf + 1) {
						base = 16;      /* if %i */
						flags &= ~PFXOK;
						goto ok;
					}
					break;
				}

				/*
				 * If we got here, c is not a legal character
				 * for a number.  Stop accumulating digits.
				 */
				break;
ok:
				/*
				 * c is legal: store it and look at the next.
				 */
				*p++ = c;
				if (--inr > 0) {
					inp++;
				} else {
					break;          /* end of input */
				}
			}
			/*
			 * If we had only a sign, it is no good; push
			 * back the sign.  If the number ends in `x',
			 * it was [sign] '0' 'x', so push back the x
			 * and treat it as [sign] '0'.
			 */
			if (flags & NDIGITS) {
				if (p > buf) {
					inp--;
					inr++;
				}
				goto match_failure;
			}
			c = p[-1];
			if (c == 'x' || c == 'X') {
				--p;
				inp--;
				inr++;
			}
			if ((flags & SUPPRESS) == 0) {
				u_quad_t res;

				*p = 0;
				if ((flags & UNSIGNED) == 0) {
					res = (u_quad_t)strtoq(buf, (char **)NULL, base);
				} else {
					res = strtouq(buf, (char **)NULL, base);
				}
				if (flags & POINTER) {
					*va_arg(ap, void **) =
					    (void *)(uintptr_t)res;
				} else if (flags & SHORTSHORT) {
					*va_arg(ap, char *) = (char)res;
				} else if (flags & SHORT) {
					*va_arg(ap, short *) = (short)res;
				} else if (flags & LONG) {
					*va_arg(ap, long *) = (long)res;
				} else if (flags & LONGLONG) {
					*va_arg(ap, long long *) = (long long)res;
				} else {
					*va_arg(ap, int *) = (int)res;
				}
				nassigned++;
			}
			nread += p - buf;
			nconversions++;
			break;
		}
	}
input_failure:
	return nconversions != 0 ? nassigned : -1;
match_failure:
	return nassigned;
}

/*
 * Fill in the given table from the scanset at the given format
 * (just after `[').  Return a pointer to the character past the
 * closing `]'.  The table has a 1 wherever characters should be
 * considered part of the scanset.
 */
static const u_char *
__sccl(char *tab, const u_char *fmt)
{
	char v;

	/* first `clear' the whole table */
	int c = *fmt++;             /* first char hat => negated scanset */
	if (c == '^') {
		v = 1;          /* default => accept */
		c = *fmt++;     /* get new first char */
	} else {
		v = 0;          /* default => reject */
	}
	/* XXX: Will not work if sizeof(tab*) > sizeof(char) */
	(void) memset(tab, v, 256);

	if (c == 0) {
		return fmt - 1;/* format ended before closing ] */
	}
	/*
	 * Now set the entries corresponding to the actual scanset
	 * to the opposite of the above.
	 *
	 * The first character may be ']' (or '-') without being special;
	 * the last character may be '-'.
	 */
	v = 1 - v;
	for (;;) {
		int n;
		tab[c] = v;             /* take character c */
doswitch:
		n = *fmt++;
		switch (n) {
		case 0:                 /* format ended too soon */
			return fmt - 1;

		case '-':
			/*
			 * A scanset of the form
			 *	[01+-]
			 * is defined as `the digit 0, the digit 1,
			 * the character +, the character -', but
			 * the effect of a scanset such as
			 *	[a-zA-Z0-9]
			 * is implementation defined.  The V7 Unix
			 * scanf treats `a-z' as `the letters a through
			 * z', but treats `a-a' as `the letter a, the
			 * character -, and the letter a'.
			 *
			 * For compatibility, the `-' is not considerd
			 * to define a range if the character following
			 * it is either a close bracket (required by ANSI)
			 * or is not numerically greater than the character
			 * we just stored in the table (c).
			 */
			n = *fmt;
			if (n == ']' || n < c) {
				c = '-';
				break;  /* resume the for(;;) */
			}
			fmt++;
			/* fill in the range */
			do {
				tab[++c] = v;
			} while (c < n);
			c = n;
			/*
			 * Alas, the V7 Unix scanf also treats formats
			 * such as [a-c-e] as `the letters a through e'.
			 * This too is permitted by the standard....
			 */
			goto doswitch;

		case ']':               /* end of scanset */
			return fmt;

		default:                /* just another character */
			c = n;
			break;
		}
	}
	/* NOTREACHED */
}
