/* $OpenBSD: magic-test.c,v 1.27 2019/01/15 09:24:59 nicm Exp $ */

/*
 * Copyright (c) 2015 Nicholas Marriott <nicm@openbsd.org>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include "config.h"

#include <endian.h>
#include <sys/types.h>

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <vis.h>

#include "magic.h"
#include "xmalloc.h"

static int magic_test_line(struct magic_line *, struct magic_state *);

static struct magic_line *magic_get_named(struct magic *m, const char *name) {
  struct magic_line ml;

  ml.name = name;
  return (RB_FIND(magic_named_tree, &m->named, &ml));
}

static enum magic_type magic_reverse_type(struct magic_state *ms,
                                          enum magic_type type) {
  if (!ms->reverse)
    return (type);
  switch (type) {
  case MAGIC_TYPE_BESHORT:
    return (MAGIC_TYPE_LESHORT);
  case MAGIC_TYPE_BELONG:
    return (MAGIC_TYPE_LELONG);
  case MAGIC_TYPE_BEQUAD:
    return (MAGIC_TYPE_LEQUAD);
  case MAGIC_TYPE_UBESHORT:
    return (MAGIC_TYPE_ULESHORT);
  case MAGIC_TYPE_UBELONG:
    return (MAGIC_TYPE_ULELONG);
  case MAGIC_TYPE_UBEQUAD:
    return (MAGIC_TYPE_ULEQUAD);
  case MAGIC_TYPE_BEFLOAT:
    return (MAGIC_TYPE_LEFLOAT);
  case MAGIC_TYPE_BEDOUBLE:
    return (MAGIC_TYPE_LEDOUBLE);
  case MAGIC_TYPE_BEDATE:
    return (MAGIC_TYPE_LEDATE);
  case MAGIC_TYPE_BEQDATE:
    return (MAGIC_TYPE_LEQDATE);
  case MAGIC_TYPE_BELDATE:
    return (MAGIC_TYPE_LELDATE);
  case MAGIC_TYPE_BEQLDATE:
    return (MAGIC_TYPE_LEQLDATE);
  case MAGIC_TYPE_UBEDATE:
    return (MAGIC_TYPE_ULEDATE);
  case MAGIC_TYPE_UBEQDATE:
    return (MAGIC_TYPE_ULEQDATE);
  case MAGIC_TYPE_UBELDATE:
    return (MAGIC_TYPE_ULELDATE);
  case MAGIC_TYPE_UBEQLDATE:
    return (MAGIC_TYPE_ULEQLDATE);
  case MAGIC_TYPE_LESHORT:
    return (MAGIC_TYPE_BESHORT);
  case MAGIC_TYPE_LELONG:
    return (MAGIC_TYPE_LELONG);
  case MAGIC_TYPE_LEQUAD:
    return (MAGIC_TYPE_LEQUAD);
  case MAGIC_TYPE_ULESHORT:
    return (MAGIC_TYPE_UBESHORT);
  case MAGIC_TYPE_ULELONG:
    return (MAGIC_TYPE_UBELONG);
  case MAGIC_TYPE_ULEQUAD:
    return (MAGIC_TYPE_UBEQUAD);
  case MAGIC_TYPE_LEFLOAT:
    return (MAGIC_TYPE_BEFLOAT);
  case MAGIC_TYPE_LEDOUBLE:
    return (MAGIC_TYPE_BEDOUBLE);
  case MAGIC_TYPE_LEDATE:
    return (MAGIC_TYPE_BEDATE);
  case MAGIC_TYPE_LEQDATE:
    return (MAGIC_TYPE_BEQDATE);
  case MAGIC_TYPE_LELDATE:
    return (MAGIC_TYPE_BELDATE);
  case MAGIC_TYPE_LEQLDATE:
    return (MAGIC_TYPE_BEQLDATE);
  case MAGIC_TYPE_ULEDATE:
    return (MAGIC_TYPE_UBEDATE);
  case MAGIC_TYPE_ULEQDATE:
    return (MAGIC_TYPE_UBEQDATE);
  case MAGIC_TYPE_ULELDATE:
    return (MAGIC_TYPE_UBELDATE);
  case MAGIC_TYPE_ULEQLDATE:
    return (MAGIC_TYPE_UBEQLDATE);
  default:
    return (type);
  }
}

static int magic_one_eq(char a, char b, int cflag) {
  if (a == b)
    return (1);
  if (cflag && islower((u_char)b) && tolower((u_char)a) == (u_char)b)
    return (1);
  return (0);
}

static int magic_test_eq(const char *ap, size_t asize, const char *bp,
                         size_t bsize, int cflag, int bflag, int Bflag) {
  size_t aoff, boff, aspaces, bspaces;

  aoff = boff = 0;
  while (aoff != asize && boff != bsize) {
    if (Bflag && isspace((u_char)ap[aoff])) {
      aspaces = 0;
      while (aoff != asize && isspace((u_char)ap[aoff])) {
        aspaces++;
        aoff++;
      }
      bspaces = 0;
      while (boff != bsize && isspace((u_char)bp[boff])) {
        bspaces++;
        boff++;
      }
      if (bspaces >= aspaces)
        continue;
      return (1);
    }
    if (magic_one_eq(ap[aoff], bp[boff], cflag)) {
      aoff++;
      boff++;
      continue;
    }
    if (bflag && isspace((u_char)bp[boff])) {
      boff++;
      continue;
    }
    if (ap[aoff] < bp[boff])
      return (-1);
    return (1);
  }
  return (0);
}

static int magic_copy_from(struct magic_state *ms, ssize_t offset, void *dst,
                           size_t size) {
  if (offset < 0)
    offset = ms->offset;
  if (offset + size > ms->size)
    return (-1);
  memcpy(dst, ms->base + offset, size);
  return (0);
}

static void magic_add_result(struct magic_state *ms, struct magic_line *ml,
                             const char *fmt, ...) {
  va_list ap;
  int separate;
  char *s, *tmp, *add;

  va_start(ap, fmt);
  if (ml->stringify) {
    if (vasprintf(&s, fmt, ap) == -1) {
      va_end(ap);
      return;
    }
    va_end(ap);
    if (asprintf(&tmp, ml->result, s) == -1) {
      free(s);
      return;
    }
    free(s);
  } else {
    if (vasprintf(&tmp, ml->result, ap) == -1) {
      va_end(ap);
      return;
    }
    va_end(ap);
  }

  separate = 1;
  if (tmp[0] == '\\' && tmp[1] == 'b') {
    separate = 0;
    add = tmp + 2;
  } else
    add = tmp;

  if (separate && *ms->out != '\0')
    strlcat(ms->out, " ", sizeof ms->out);
  strlcat(ms->out, add, sizeof ms->out);

  free(tmp);
}

static void magic_add_string(struct magic_state *ms, struct magic_line *ml,
                             const char *s, size_t slen) {
  char *out;
  size_t outlen, offset;

  outlen = MAGIC_STRING_SIZE;
  if (outlen > slen)
    outlen = slen;
  for (offset = 0; offset < outlen; offset++) {
    if (s[offset] == '\0' || !isprint((u_char)s[offset])) {
      outlen = offset;
      break;
    }
  }
  out = xreallocarray(NULL, 4, outlen + 1);
  strvisx(out, s, outlen, VIS_TAB | VIS_NL | VIS_CSTYLE | VIS_OCTAL);
  magic_add_result(ms, ml, "%s", out);
  free(out);
}

static int magic_test_signed(struct magic_line *ml, int64_t value,
                             int64_t wanted) {
  switch (ml->test_operator) {
  case 'x':
    return (1);
  case '<':
    return (value < wanted);
  case '[':
    return (value <= wanted);
  case '>':
    return (value > wanted);
  case ']':
    return (value >= wanted);
  case '=':
    return (value == wanted);
  case '&':
    return ((value & wanted) == wanted);
  case '^':
    return ((~value & wanted) == wanted);
  }
  return (-1);
}

static int magic_test_unsigned(struct magic_line *ml, uint64_t value,
                               uint64_t wanted) {
  switch (ml->test_operator) {
  case 'x':
    return (1);
  case '<':
    return (value < wanted);
  case '[':
    return (value <= wanted);
  case '>':
    return (value > wanted);
  case ']':
    return (value >= wanted);
  case '=':
    return (value == wanted);
  case '&':
    return ((value & wanted) == wanted);
  case '^':
    return ((~value & wanted) == wanted);
  }
  return (-1);
}

static int magic_test_double(struct magic_line *ml, double value,
                             double wanted) {
  switch (ml->test_operator) {
  case 'x':
    return (1);
  case '=':
    return (value == wanted);
  }
  return (-1);
}

static int magic_test_type_none(__unused struct magic_line *ml,
                                __unused struct magic_state *ms) {
  return (0);
}

static int magic_test_type_byte(struct magic_line *ml, struct magic_state *ms) {
  int8_t value;
  int result;

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);

  if (ml->type_operator == '&')
    value &= (int8_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (int8_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (int8_t)ml->type_operand;
  else if (ml->type_operator == '/')
    value /= (int8_t)ml->type_operand;
  else if (ml->type_operator == '%')
    value %= (int8_t)ml->type_operand;
  else if (ml->type_operator == '*')
    value *= (int8_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_signed(ml, value, (int8_t)ml->test_signed);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%c", (int)value);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_short(struct magic_line *ml,
                                 struct magic_state *ms) {
  int16_t value;
  int result;

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_BESHORT))
    value = be16toh(value);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_LESHORT))
    value = le16toh(value);

  if (ml->type_operator == '&')
    value &= (int16_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (int16_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (int16_t)ml->type_operand;
  else if (ml->type_operator == '/')
    value /= (int16_t)ml->type_operand;
  else if (ml->type_operator == '%')
    value %= (int16_t)ml->type_operand;
  else if (ml->type_operator == '*')
    value *= (int16_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_signed(ml, value, (int16_t)ml->test_signed);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%hd", (int)value);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_long(struct magic_line *ml, struct magic_state *ms) {
  int32_t value;
  int result;

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_BELONG))
    value = be32toh(value);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_LELONG))
    value = le32toh(value);

  if (ml->type_operator == '&')
    value &= (int32_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (int32_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (int32_t)ml->type_operand;
  else if (ml->type_operator == '/')
    value /= (int32_t)ml->type_operand;
  else if (ml->type_operator == '%')
    value %= (int32_t)ml->type_operand;
  else if (ml->type_operator == '*')
    value *= (int32_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_signed(ml, value, (int32_t)ml->test_signed);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%d", (int)value);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_quad(struct magic_line *ml, struct magic_state *ms) {
  int64_t value;
  int result;

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_BEQUAD))
    value = be64toh(value);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_LEQUAD))
    value = le64toh(value);

  if (ml->type_operator == '&')
    value &= (int64_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (int64_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (int64_t)ml->type_operand;
  else if (ml->type_operator == '/')
    value /= (int64_t)ml->type_operand;
  else if (ml->type_operator == '%')
    value %= (int64_t)ml->type_operand;
  else if (ml->type_operator == '*')
    value *= (int64_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_signed(ml, value, (int64_t)ml->test_signed);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%lld", (long long)value);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_ubyte(struct magic_line *ml,
                                 struct magic_state *ms) {
  uint8_t value;
  int result;

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);

  if (ml->type_operator == '&')
    value &= (uint8_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (uint8_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (uint8_t)ml->type_operand;
  else if (ml->type_operator == '/')
    value /= (uint8_t)ml->type_operand;
  else if (ml->type_operator == '%')
    value %= (uint8_t)ml->type_operand;
  else if (ml->type_operator == '*')
    value *= (uint8_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_unsigned(ml, value, (uint8_t)ml->test_unsigned);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%c", (unsigned int)value);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_ushort(struct magic_line *ml,
                                  struct magic_state *ms) {
  uint16_t value;
  int result;

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == MAGIC_TYPE_UBESHORT)
    value = be16toh(value);
  if (ml->type == MAGIC_TYPE_ULESHORT)
    value = le16toh(value);

  if (ml->type_operator == '&')
    value &= (uint16_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (uint16_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (uint16_t)ml->type_operand;
  else if (ml->type_operator == '/')
    value /= (uint16_t)ml->type_operand;
  else if (ml->type_operator == '%')
    value %= (uint16_t)ml->type_operand;
  else if (ml->type_operator == '*')
    value *= (uint16_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_unsigned(ml, value, (uint16_t)ml->test_unsigned);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%hu", (unsigned int)value);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_ulong(struct magic_line *ml,
                                 struct magic_state *ms) {
  uint32_t value;
  int result;

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == MAGIC_TYPE_UBELONG)
    value = be32toh(value);
  if (ml->type == MAGIC_TYPE_ULELONG)
    value = le32toh(value);

  if (ml->type_operator == '&')
    value &= (uint32_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (uint32_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (uint32_t)ml->type_operand;
  else if (ml->type_operator == '/')
    value /= (uint32_t)ml->type_operand;
  else if (ml->type_operator == '%')
    value %= (uint32_t)ml->type_operand;
  else if (ml->type_operator == '*')
    value *= (uint32_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_unsigned(ml, value, (uint32_t)ml->test_unsigned);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%u", (unsigned int)value);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_uquad(struct magic_line *ml,
                                 struct magic_state *ms) {
  uint64_t value;
  int result;

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == MAGIC_TYPE_UBEQUAD)
    value = be64toh(value);
  if (ml->type == MAGIC_TYPE_ULEQUAD)
    value = le64toh(value);

  if (ml->type_operator == '&')
    value &= (uint64_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (uint64_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (uint64_t)ml->type_operand;
  else if (ml->type_operator == '/')
    value /= (uint64_t)ml->type_operand;
  else if (ml->type_operator == '%')
    value %= (uint64_t)ml->type_operand;
  else if (ml->type_operator == '*')
    value *= (uint64_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_unsigned(ml, value, (uint64_t)ml->test_unsigned);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%llu", (unsigned long long)value);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_float(struct magic_line *ml,
                                 struct magic_state *ms) {
  uint32_t value0;
  float value;
  int result;

  if (magic_copy_from(ms, -1, &value0, sizeof value0) != 0)
    return (0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_BEFLOAT))
    value0 = be32toh(value0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_LEFLOAT))
    value0 = le32toh(value0);
  memcpy(&value, &value0, sizeof value);

  if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_double(ml, value, (float)ml->test_double);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%g", value);
    ms->offset += sizeof value0;
  }
  return (1);
}

static int magic_test_type_double(struct magic_line *ml,
                                  struct magic_state *ms) {
  uint64_t value0;
  double value;
  int result;

  if (magic_copy_from(ms, -1, &value0, sizeof value0) != 0)
    return (0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_BEDOUBLE))
    value0 = be64toh(value0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_LEDOUBLE))
    value0 = le64toh(value0);
  memcpy(&value, &value0, sizeof value);

  if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_double(ml, value, (double)ml->test_double);
  if (result == !ml->test_not && ml->result != NULL) {
    magic_add_result(ms, ml, "%g", value);
    ms->offset += sizeof value0;
  }
  return (1);
}

static int magic_test_type_string(struct magic_line *ml,
                                  struct magic_state *ms) {
  const char *s, *cp;
  size_t slen;
  int result, cflag = 0, bflag = 0, Bflag = 0;

  cp = &ml->type_string[(sizeof "string") - 1];
  if (*cp != '\0') {
    if (*cp != '/')
      return (-1);
    cp++;
    for (; *cp != '\0'; cp++) {
      switch (*cp) {
      case 'B':
      case 'W':
        Bflag = 1;
        break;
      case 'b':
      case 'w':
        bflag = 1;
        break;
      case 'c':
        cflag = 1;
        break;
      case 't':
        break;
      default:
        return (-1);
      }
    }
  }

  s = ms->base + ms->offset;
  slen = ms->size - ms->offset;
  if (slen < ml->test_string_size)
    return (0);

  result = magic_test_eq(s, slen, ml->test_string, ml->test_string_size, cflag,
                         bflag, Bflag);
  switch (ml->test_operator) {
  case 'x':
    result = 1;
    break;
  case '<':
    result = result < 0;
    break;
  case '>':
    result = result > 0;
    break;
  case '=':
    slen = ml->test_string_size; /* only print what was found */
    result = result == 0;
    break;
  default:
    result = -1;
    break;
  }
  if (result == !ml->test_not) {
    if (ml->result != NULL)
      magic_add_string(ms, ml, s, slen);
    if (result && ml->test_operator == '=')
      ms->offset = s - ms->base + ml->test_string_size;
  }
  return (result);
}

static int magic_test_type_pstring(struct magic_line *ml,
                                   struct magic_state *ms) {
  const char *s, *cp;
  size_t slen;
  int result;

  cp = &ml->type_string[(sizeof "pstring") - 1];
  if (*cp != '\0') {
    if (*cp != '/')
      return (-1);
    cp++;
    for (; *cp != '\0'; cp++) {
      switch (*cp) {
      default:
        return (-1);
      }
    }
  }

  s = ms->base + ms->offset;
  if (ms->size - ms->offset < 1)
    return (-1);
  slen = *(u_char *)s;
  if (slen + 1 > ms->size - ms->offset)
    return (-1);
  s++;

  if (slen < ml->test_string_size)
    result = -1;
  else if (slen > ml->test_string_size)
    result = 1;
  else
    result = memcmp(s, ml->test_string, ml->test_string_size);
  switch (ml->test_operator) {
  case 'x':
    result = 1;
    break;
  case '<':
    result = result < 0;
    break;
  case '>':
    result = result > 0;
    break;
  case '=':
    result = result == 0;
    break;
  default:
    result = -1;
    break;
  }
  if (result == !ml->test_not) {
    if (ml->result != NULL)
      magic_add_string(ms, ml, s, slen);
    if (result && ml->test_operator == '=')
      ms->offset += slen + 1;
  }
  return (result);
}

static int magic_test_type_date(struct magic_line *ml, struct magic_state *ms) {
  int32_t value;
  int result;
  time_t t;
  char s[64];

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_BEDATE) ||
      ml->type == magic_reverse_type(ms, MAGIC_TYPE_BELDATE))
    value = be32toh(value);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_LEDATE) ||
      ml->type == magic_reverse_type(ms, MAGIC_TYPE_LELDATE))
    value = le32toh(value);

  if (ml->type_operator == '&')
    value &= (int32_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (int32_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (int32_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_signed(ml, value, (int32_t)ml->test_signed);
  if (result == !ml->test_not && ml->result != NULL) {
    t = value;
    switch (ml->type) {
    case MAGIC_TYPE_LDATE:
    case MAGIC_TYPE_LELDATE:
    case MAGIC_TYPE_BELDATE:
      ctime_r(&t, s);
      break;
    default:
      asctime_r(gmtime(&t), s);
      break;
    }
    s[strcspn(s, "\n")] = '\0';
    magic_add_result(ms, ml, "%s", s);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_qdate(struct magic_line *ml,
                                 struct magic_state *ms) {
  int64_t value;
  int result;
  time_t t;
  char s[64];

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_BEQDATE) ||
      ml->type == magic_reverse_type(ms, MAGIC_TYPE_BEQLDATE))
    value = be64toh(value);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_LEQDATE) ||
      ml->type == magic_reverse_type(ms, MAGIC_TYPE_LEQLDATE))
    value = le64toh(value);

  if (ml->type_operator == '&')
    value &= (int64_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (int64_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (int64_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_signed(ml, value, (int64_t)ml->test_signed);
  if (result == !ml->test_not && ml->result != NULL) {
    t = value;
    switch (ml->type) {
    case MAGIC_TYPE_QLDATE:
    case MAGIC_TYPE_LEQLDATE:
    case MAGIC_TYPE_BEQLDATE:
      ctime_r(&t, s);
      break;
    default:
      asctime_r(gmtime(&t), s);
      break;
    }
    s[strcspn(s, "\n")] = '\0';
    magic_add_result(ms, ml, "%s", s);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_udate(struct magic_line *ml,
                                 struct magic_state *ms) {
  uint32_t value;
  int result;
  time_t t;
  char s[64];

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_BEDATE) ||
      ml->type == magic_reverse_type(ms, MAGIC_TYPE_BELDATE))
    value = be32toh(value);
  if (ml->type == magic_reverse_type(ms, MAGIC_TYPE_LEDATE) ||
      ml->type == magic_reverse_type(ms, MAGIC_TYPE_LELDATE))
    value = le32toh(value);

  if (ml->type_operator == '&')
    value &= (uint32_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (uint32_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (uint32_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_unsigned(ml, value, (uint32_t)ml->test_unsigned);
  if (result == !ml->test_not && ml->result != NULL) {
    t = value;
    switch (ml->type) {
    case MAGIC_TYPE_LDATE:
    case MAGIC_TYPE_LELDATE:
    case MAGIC_TYPE_BELDATE:
      ctime_r(&t, s);
      break;
    default:
      asctime_r(gmtime(&t), s);
      break;
    }
    s[strcspn(s, "\n")] = '\0';
    magic_add_result(ms, ml, "%s", s);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_uqdate(struct magic_line *ml,
                                  struct magic_state *ms) {
  uint64_t value;
  int result;
  time_t t;
  char s[64];

  if (magic_copy_from(ms, -1, &value, sizeof value) != 0)
    return (0);
  if (ml->type == MAGIC_TYPE_UBEQDATE || ml->type == MAGIC_TYPE_UBEQLDATE)
    value = be64toh(value);
  if (ml->type == MAGIC_TYPE_ULEQDATE || ml->type == MAGIC_TYPE_ULEQLDATE)
    value = le64toh(value);

  if (ml->type_operator == '&')
    value &= (uint64_t)ml->type_operand;
  else if (ml->type_operator == '-')
    value -= (uint64_t)ml->type_operand;
  else if (ml->type_operator == '+')
    value += (uint64_t)ml->type_operand;
  else if (ml->type_operator != ' ')
    return (-1);

  result = magic_test_unsigned(ml, value, (uint64_t)ml->test_unsigned);
  if (result == !ml->test_not && ml->result != NULL) {
    t = value;
    switch (ml->type) {
    case MAGIC_TYPE_UQLDATE:
    case MAGIC_TYPE_ULEQLDATE:
    case MAGIC_TYPE_UBEQLDATE:
      ctime_r(&t, s);
      break;
    default:
      asctime_r(gmtime(&t), s);
      break;
    }
    s[strcspn(s, "\n")] = '\0';
    magic_add_result(ms, ml, "%s", s);
    ms->offset += sizeof value;
  }
  return (result);
}

static int magic_test_type_bestring16(__unused struct magic_line *ml,
                                      __unused struct magic_state *ms) {
  return (-2);
}

static int magic_test_type_lestring16(__unused struct magic_line *ml,
                                      __unused struct magic_state *ms) {
  return (-2);
}

static int magic_test_type_melong(__unused struct magic_line *ml,
                                  __unused struct magic_state *ms) {
  return (-2);
}

static int magic_test_type_medate(__unused struct magic_line *ml,
                                  __unused struct magic_state *ms) {
  return (-2);
}

static int magic_test_type_meldate(__unused struct magic_line *ml,
                                   __unused struct magic_state *ms) {
  return (-2);
}

static int magic_test_type_regex(struct magic_line *ml,
                                 struct magic_state *ms) {
  const char *cp;
  regex_t re;
  regmatch_t m;
  int result, flags = 0, sflag = 0;

  cp = &ml->type_string[(sizeof "regex") - 1];
  if (*cp != '\0') {
    if (*cp != '/')
      return (-1);
    cp++;
    for (; *cp != '\0'; cp++) {
      switch (*cp) {
      case 's':
        sflag = 1;
        break;
      case 'c':
        flags |= REG_ICASE;
        break;
      default:
        return (-1);
      }
    }
  }

  if (regcomp(&re, ml->test_string, REG_EXTENDED | REG_NEWLINE | flags) != 0)
    return (-1);
  m.rm_so = ms->offset;
  m.rm_eo = ms->size;

  result = (regexec(&re, ms->base, 1, &m, REG_STARTEND) == 0);
  if (result == !ml->test_not) {
    if (ml->result != NULL) {
      magic_add_string(ms, ml, ms->base + m.rm_so, m.rm_eo - m.rm_so);
    }
    if (result) {
      if (sflag)
        ms->offset = m.rm_so;
      else
        ms->offset = m.rm_eo;
    }
  }
  regfree(&re);
  return (result);
}

static int magic_test_type_search(struct magic_line *ml,
                                  struct magic_state *ms) {
  const char *cp, *endptr, *start, *found;
  size_t size, end, i;
  uint64_t range;
  int result, n, cflag = 0, bflag = 0, Bflag = 0;

  cp = &ml->type_string[(sizeof "search") - 1];
  if (*cp != '\0') {
    if (*cp != '/')
      return (-1);
    cp++;

    endptr = magic_strtoull(cp, &range);
    if (endptr == NULL || (*endptr != '/' && *endptr != '\0'))
      return (-1);

    if (*endptr == '/') {
      for (cp = endptr + 1; *cp != '\0'; cp++) {
        switch (*cp) {
        case 'B':
        case 'W':
          Bflag = 1;
          break;
        case 'b':
        case 'w':
          bflag = 1;
          break;
        case 'c':
          cflag = 1;
          break;
        case 't':
          break;
        default:
          return (-1);
        }
      }
    }
  } else
    range = UINT64_MAX;
  if (range > (uint64_t)ms->size - ms->offset)
    range = ms->size - ms->offset;
  size = ml->test_string_size;

  /* Want to search every starting position from up to range + size. */
  end = range + size;
  if (end > ms->size - ms->offset) {
    if (size > ms->size - ms->offset)
      end = 0;
    else
      end = ms->size - ms->offset - size;
  }

  /*
   * < and > and the flags are only in /etc/magic with search/1 so don't
   * support them with anything else.
   */
  start = ms->base + ms->offset;
  if (end == 0)
    found = NULL;
  else if (ml->test_operator == 'x')
    found = start;
  else if (range == 1) {
    n = magic_test_eq(start, ms->size - ms->offset, ml->test_string, size,
                      cflag, bflag, Bflag);
    if (n == -1 && ml->test_operator == '<')
      found = start;
    else if (n == 1 && ml->test_operator == '>')
      found = start;
    else if (n == 0 && ml->test_operator == '=')
      found = start;
    else
      found = NULL;
  } else {
    if (ml->test_operator != '=')
      return (-2);
    for (i = 0; i < end; i++) {
      n = magic_test_eq(start + i, ms->size - ms->offset - i, ml->test_string,
                        size, cflag, bflag, Bflag);
      if (n == 0) {
        found = start + i;
        break;
      }
    }
    if (i == end)
      found = NULL;
  }
  result = (found != NULL);

  if (result == !ml->test_not) {
    if (ml->result != NULL)
      magic_add_string(ms, ml, found, ms->size - ms->offset);
    if (result && found != NULL && ml->test_operator == '=')
      ms->offset = (found + size) - ms->base;
  }
  return (result);
}

static int magic_test_type_default(struct magic_line *ml,
                                   struct magic_state *ms) {
  if (!ms->matched && ml->result != NULL)
    magic_add_result(ms, ml, "%s", "");
  return (!ms->matched);
}

static int magic_test_type_clear(struct magic_line *ml,
                                 struct magic_state *ms) {
  if (ml->result != NULL)
    magic_add_result(ms, ml, "%s", "");
  return (1);
}

static int magic_test_type_name(__unused struct magic_line *ml,
                                __unused struct magic_state *ms) {
  return (-1);
}

static int magic_test_type_use(__unused struct magic_line *ml,
                               __unused struct magic_state *ms) {
  return (1);
}

static int (*magic_test_functions[])(struct magic_line *,
                                     struct magic_state *) = {
    magic_test_type_none,       magic_test_type_byte,
    magic_test_type_short,      magic_test_type_long,
    magic_test_type_quad,       magic_test_type_ubyte,
    magic_test_type_ushort,     magic_test_type_ulong,
    magic_test_type_uquad,      magic_test_type_float,
    magic_test_type_double,     magic_test_type_string,
    magic_test_type_pstring,    magic_test_type_date,
    magic_test_type_qdate,      magic_test_type_date,
    magic_test_type_qdate,      magic_test_type_udate,
    magic_test_type_uqdate,     magic_test_type_udate,
    magic_test_type_qdate,      magic_test_type_short,
    magic_test_type_long,       magic_test_type_quad,
    magic_test_type_ushort,     magic_test_type_ulong,
    magic_test_type_uquad,      magic_test_type_float,
    magic_test_type_double,     magic_test_type_date,
    magic_test_type_qdate,      magic_test_type_date,
    magic_test_type_qdate,      magic_test_type_udate,
    magic_test_type_uqdate,     magic_test_type_udate,
    magic_test_type_uqdate,     magic_test_type_bestring16,
    magic_test_type_short,      magic_test_type_long,
    magic_test_type_quad,       magic_test_type_ushort,
    magic_test_type_ulong,      magic_test_type_uquad,
    magic_test_type_float,      magic_test_type_double,
    magic_test_type_date,       magic_test_type_qdate,
    magic_test_type_date,       magic_test_type_qdate,
    magic_test_type_udate,      magic_test_type_uqdate,
    magic_test_type_udate,      magic_test_type_uqdate,
    magic_test_type_lestring16, magic_test_type_melong,
    magic_test_type_medate,     magic_test_type_meldate,
    magic_test_type_regex,      magic_test_type_search,
    magic_test_type_default,    magic_test_type_clear,
    magic_test_type_name,       magic_test_type_use,
};

static void magic_test_children(struct magic_line *ml, struct magic_state *ms,
                                size_t start, int reverse) {
  struct magic_line *child;
  size_t saved_start, saved_offset;
  int saved_reverse;

  saved_start = ms->start;
  saved_reverse = ms->reverse;
  saved_offset = ms->offset;

  ms->matched = 0; /* no need to save, caller will set too */

  TAILQ_FOREACH(child, &ml->children, entry) {
    ms->start = start;
    ms->reverse = reverse;
    ms->offset = saved_offset;

    magic_test_line(child, ms);
  }

  ms->start = saved_start;
  ms->reverse = saved_reverse;
  ms->offset = saved_offset;
}

static int magic_test_line(struct magic_line *ml, struct magic_state *ms) {
  struct magic *m = ml->root;
  struct magic_line *named;
  int64_t offset, wanted, next;
  int result;
  uint8_t b;
  uint16_t s;
  uint32_t l;

  if (ml->indirect_type == ' ')
    wanted = ms->start + ml->offset;
  else {
    wanted = ml->indirect_offset;
    if (ml->indirect_relative) {
      if (wanted < 0 && (size_t)-wanted > ms->offset)
        return (0);
      if (wanted > 0 && ms->offset + wanted > ms->size)
        return (0);
      next = ms->offset + ml->indirect_offset;
    } else
      next = wanted;

    switch (ml->indirect_type) {
    case 'b':
    case 'B':
      if (magic_copy_from(ms, next, &b, sizeof b) != 0)
        return (0);
      wanted = b;
      break;
    case 's':
      if (magic_copy_from(ms, next, &s, sizeof s) != 0)
        return (0);
      wanted = le16toh(s);
      break;
    case 'S':
      if (magic_copy_from(ms, next, &s, sizeof s) != 0)
        return (0);
      wanted = be16toh(s);
      break;
    case 'l':
      if (magic_copy_from(ms, next, &l, sizeof l) != 0)
        return (0);
      wanted = le16toh(l);
      break;
    case 'L':
      if (magic_copy_from(ms, next, &l, sizeof l) != 0)
        return (0);
      wanted = be16toh(l);
      break;
    }

    switch (ml->indirect_operator) {
    case '+':
      wanted += ml->indirect_operand;
      break;
    case '-':
      wanted -= ml->indirect_operand;
      break;
    case '*':
      wanted *= ml->indirect_operand;
      break;
    }
  }

  if (ml->offset_relative) {
    if (wanted < 0 && (size_t)-wanted > ms->offset)
      return (0);
    if (wanted > 0 && ms->offset + wanted > ms->size)
      return (0);
    offset = ms->offset + wanted;
  } else
    offset = wanted;
  if (offset < 0 || (size_t)offset > ms->size)
    return (0);
  ms->offset = offset; /* test function may update */

  result = magic_test_functions[ml->type](ml, ms);
  if (result == -1) {
    magic_warn(ml, "test %s/%c failed", ml->type_string, ml->test_operator);
    return (0);
  }
  if (result == -2) {
    magic_warn(ml, "test %s/%c not implemented", ml->type_string,
               ml->test_operator);
    return (0);
  }
  if (result == ml->test_not)
    return (0);
  if (ml->mimetype != NULL)
    ms->mimetype = ml->mimetype;

  magic_warn(ml,
             "test %s/%c matched at offset %lld (now %zu): "
             "'%s'",
             ml->type_string, ml->test_operator, offset, ms->offset,
             ml->result == NULL ? "" : ml->result);

  if (ml->type == MAGIC_TYPE_USE) {
    if (*ml->name == '^')
      named = magic_get_named(m, ml->name + 1);
    else
      named = magic_get_named(m, ml->name);
    if (named == NULL) {
      magic_warn(ml, "no name found for use %s", ml->name);
      return (0);
    }
    magic_warn(ml, "use %s at offset %lld", ml->name, offset);
    magic_test_children(named, ms, offset, *ml->name == '^');
  }

  magic_test_children(ml, ms, ms->start, ms->reverse);

  if (ml->type == MAGIC_TYPE_CLEAR)
    ms->matched = 0;
  else
    ms->matched = 1;
  return (ml->result != NULL);
}

const char *magic_test(struct magic *m, const void *base, size_t size,
                       int flags) {
  struct magic_line *ml;
  static struct magic_state ms;

  memset(&ms, 0, sizeof ms);

  ms.base = base;
  ms.size = size;

  ms.text = !!(flags & MAGIC_TEST_TEXT);

  RB_FOREACH(ml, magic_tree, &m->tree) {
    ms.offset = 0;
    if (ml->text == ms.text && magic_test_line(ml, &ms))
      break;
  }

  if (*ms.out != '\0') {
    if (flags & MAGIC_TEST_MIME) {
      if (ms.mimetype != NULL)
        return (ms.mimetype);
      return (NULL);
    }
    return (ms.out);
  }
  return (NULL);
}
