/* Construct a regular expression from a literal string.
   Copyright (C) 1995, 2010-2025 Free Software Foundation, Inc.
   Written by Bruno Haible <haible@clisp.cons.org>, 2010.

   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 <https://www.gnu.org/licenses/>.  */

#include <config.h>

/* Specification.  */
#include "regex-quote.h"

#include <string.h>

#if GNULIB_MCEL_PREFER
# include "mcel.h"
#else
# include "mbuiter.h"
#endif

#include "xalloc.h"

/* Characters that are special in a BRE.  */
static const char bre_special[] = "$^.*[\\";

/* Characters that are special in an ERE.  */
static const char ere_special[] = "$^.*[\\+?{()|";

struct regex_quote_spec
regex_quote_spec_posix (int cflags, bool anchored)
{
  struct regex_quote_spec result;

  strcpy (result.special, cflags != 0 ? ere_special : bre_special);
  result.multibyte = true;
  result.anchored = anchored;

  return result;
}

/* Syntax bit values, defined in GNU <regex.h>.  We don't include it here,
   otherwise this module would need to depend on gnulib module 'regex'.  */
#define RE_BK_PLUS_QM    0x00000002
#define RE_INTERVALS     0x00000200
#define RE_LIMITED_OPS   0x00000400
#define RE_NEWLINE_ALT   0x00000800
#define RE_NO_BK_BRACES  0x00001000
#define RE_NO_BK_PARENS  0x00002000
#define RE_NO_BK_VBAR    0x00008000

struct regex_quote_spec
regex_quote_spec_gnu (unsigned long /*reg_syntax_t*/ syntax, bool anchored)
{
  struct regex_quote_spec result;
  char *p;

  p = result.special;
  memcpy (p, bre_special, sizeof (bre_special) - 1);
  p += sizeof (bre_special) - 1;
  if ((syntax & RE_LIMITED_OPS) == 0 && (syntax & RE_BK_PLUS_QM) == 0)
    {
      *p++ = '+';
      *p++ = '?';
    }
  if ((syntax & RE_INTERVALS) != 0 && (syntax & RE_NO_BK_BRACES) != 0)
    {
      *p++ = '{';
      *p++ = '}';
    }
  if ((syntax & RE_NO_BK_PARENS) != 0)
    {
      *p++ = '(';
      *p++ = ')';
    }
  if ((syntax & RE_LIMITED_OPS) == 0 && (syntax & RE_NO_BK_VBAR) != 0)
    *p++ = '|';
  if ((syntax & RE_NEWLINE_ALT) != 0)
    *p++ = '\n';
  *p = '\0';

  result.multibyte = true;
  result.anchored = anchored;

  return result;
}

/* Characters that are special in a PCRE.  */
static const char pcre_special[] = "$^.*[]\\+?{}()|";

/* Options bit values, defined in <pcre.h>.  We don't include it here, because
   it is not a standard header.  */
#define PCRE_ANCHORED 0x00000010
#define PCRE_EXTENDED 0x00000008

struct regex_quote_spec
regex_quote_spec_pcre (int options, bool anchored)
{
  struct regex_quote_spec result;
  char *p;

  p = result.special;
  memcpy (p, pcre_special, sizeof (pcre_special) - 1);
  p += sizeof (pcre_special) - 1;
  if (options & PCRE_EXTENDED)
    {
      *p++ = ' ';
      *p++ = '\t';
      *p++ = '\n';
      *p++ = '\v';
      *p++ = '\f';
      *p++ = '\r';
      *p++ = '#';
    }
  *p = '\0';

  /* PCRE regular expressions consist of UTF-8 characters of options contains
     PCRE_UTF8 and of single bytes otherwise.  */
  result.multibyte = false;
  /* If options contains PCRE_ANCHORED, the anchoring is implicit.  */
  result.anchored = (options & PCRE_ANCHORED ? 0 : anchored);

  return result;
}

size_t
regex_quote_length (const char *string, const struct regex_quote_spec *spec)
{
  const char *special = spec->special;
  size_t length;

  length = 0;
  if (spec->anchored)
    length += 2; /* for '^' at the beginning and '$' at the end */
  if (spec->multibyte)
    {
#if GNULIB_MCEL_PREFER
      char const *iter = string;
      for (mcel_t g; *iter; iter += g.len)
        {
          g = mcel_scanz (iter);
          /* We know that special contains only ASCII characters.  */
          length += g.len == 1 && strchr (special, *iter);
        }
      length += iter - string;
#else
      mbui_iterator_t iter;

      for (mbui_init (iter, string); mbui_avail (iter); mbui_advance (iter))
        {
          /* We know that special contains only ASCII characters.  */
          if (mb_len (mbui_cur (iter)) == 1
              && strchr (special, * mbui_cur_ptr (iter)))
            length += 1;
          length += mb_len (mbui_cur (iter));
        }
#endif
    }
  else
    {
      const char *iter;

      for (iter = string; *iter != '\0'; iter++)
        {
          if (strchr (special, *iter))
            length += 1;
          length += 1;
        }
    }

  return length;
}

char *
regex_quote_copy (char *p, const char *string, const struct regex_quote_spec *spec)
{
  const char *special = spec->special;

  if (spec->anchored)
    *p++ = '^';
  if (spec->multibyte)
    {
#if GNULIB_MCEL_PREFER
      for (char const *iter = string; *iter; )
        {
          mcel_t g = mcel_scanz (iter);
          *p = '\\';
          /* We know that special contains only ASCII characters.  */
          p += g.len == 1 && strchr (special, *iter);
          p = mempcpy (p, iter, g.len);
          iter += g.len;
        }
#else
      mbui_iterator_t iter;

      for (mbui_init (iter, string); mbui_avail (iter); mbui_advance (iter))
        {
          /* We know that special contains only ASCII characters.  */
          if (mb_len (mbui_cur (iter)) == 1
              && strchr (special, * mbui_cur_ptr (iter)))
            *p++ = '\\';
          memcpy (p, mbui_cur_ptr (iter), mb_len (mbui_cur (iter)));
          p += mb_len (mbui_cur (iter));
        }
#endif
    }
  else
    {
      const char *iter;

      for (iter = string; *iter != '\0'; iter++)
        {
          if (strchr (special, *iter))
            *p++ = '\\';
          *p++ = *iter++;
        }
    }
  if (spec->anchored)
    *p++ = '$';

  return p;
}

char *
regex_quote (const char *string, const struct regex_quote_spec *spec)
{
  size_t length = regex_quote_length (string, spec);
  char *result = XNMALLOC (length + 1, char);
  char *p;

  p = result;
  p = regex_quote_copy (p, string, spec);
  *p = '\0';
  return result;
}
