/* Copyright (C) 2021-2025 Free Software Foundation, Inc.
   Contributed by Oracle.

   This file is part of GNU Binutils.

   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, 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, write to the Free Software
   Foundation, 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "config.h"
#include <ctype.h>

#include "util.h"
#include "vec.h"
#include "DefaultHandler.h"
#include "SAXParser.h"
#include "SAXParserFactory.h"
#include "StringBuilder.h"

/*
 *  Private implementation of Attributes
 */
class AttributesP : public Attributes
{
public:
  AttributesP ();
  ~AttributesP ();
  int getLength ();
  const char *getQName (int index);
  const char *getValue (int index);
  int getIndex (const char *qName);
  const char *getValue (const char *qName);
  void append (char *qName, char *value);

private:
  Vector<char*> *names;
  Vector<char*> *values;
};

AttributesP::AttributesP ()
{
  names = new Vector<char*>;
  values = new Vector<char*>;
}

AttributesP::~AttributesP ()
{
  Destroy (names);
  Destroy (values);
}

int
AttributesP::getLength ()
{
  return names->size ();
}

const char *
AttributesP::getQName (int index)
{
  if (index < 0 || index >= names->size ())
    return NULL;
  return names->fetch (index);
}

const char *
AttributesP::getValue (int index)
{
  if (index < 0 || index >= values->size ())
    return NULL;
  return values->fetch (index);
}

int
AttributesP::getIndex (const char *qName)
{
  for (int idx = 0; idx < names->size (); idx++)
    if (strcmp (names->fetch (idx), qName) == 0)
      return idx;
  return -1;
}

const char *
AttributesP::getValue (const char *qName)
{
  for (int idx = 0; idx < names->size (); idx++)
    if (strcmp (names->fetch (idx), qName) == 0)
      return values->fetch (idx);
  return NULL;
}

void
AttributesP::append (char *qName, char *value)
{
  names->append (qName);
  values->append (value);
}

/*
 *  Implementation of SAXException
 */
SAXException::SAXException ()
{
  message = xstrdup ("null");
}

SAXException::SAXException (const char *_message)
{
  if (_message == NULL)
    message = xstrdup ("null");
  else
    message = xstrdup (_message);
}

SAXException::~SAXException ()
{
  free (message);
}

char *
SAXException::getMessage ()
{
  return message;
}

/*
 *  SAXParseException
 */
SAXParseException::SAXParseException (char *message, int _lineNumber, int _columnNumber)
: SAXException (message == NULL ? GTXT ("XML parse error") : message)
{
  lineNumber = _lineNumber;
  columnNumber = _columnNumber;
}

/*
 *  Private implementation of SAXParser
 */
class SAXParserP : public SAXParser
{
public:
  SAXParserP ();
  ~SAXParserP ();
  void reset ();
  void parse (File*, DefaultHandler*);

  bool
  isNamespaceAware ()
  {
    return false;
  }

  bool
  isValidating ()
  {
    return false;
  }

private:

  static const int CH_EOF = -1;

  void nextch ();
  bool isWSpace ();
  void skipWSpaces ();
  void scanString (const char *str);
  char *parseName ();
  char *parseString ();
  char *decodeString (char *str);
  Attributes *parseAttributes ();
  void parseTag ();
  void parseDocument ();
  void parsePart (int idx);

  DefaultHandler *dh;
  int bufsz;
  char *buffer;
  int cntsz;
  int idx;
  int curch;
  int line;
  int column;
};

SAXParserP::SAXParserP ()
{
  dh = NULL;
  bufsz = 0x2000;
  buffer = (char*) xmalloc (bufsz);
  cntsz = 0;
  idx = 0;
  line = 1;
  column = 0;
}

SAXParserP::~SAXParserP ()
{
  free (buffer);
}

void
SAXParserP::reset ()
{
  dh = NULL;
  bufsz = 8192;
  buffer = (char*) xrealloc (buffer, bufsz);
  cntsz = 0;
  idx = 0;
  line = 1;
  column = 0;
}

void
SAXParserP::parse (File *f, DefaultHandler *_dh)
{
  if (_dh == NULL)
    return;
  dh = _dh;
  FILE *file = (FILE*) f;
  int rem = bufsz;
  cntsz = 0;
  idx = 0;
  for (;;)
    {
      int n = (int) fread (buffer + cntsz, 1, rem, file);
      if (ferror (file) || n <= 0)
	break;
      cntsz += n;
      if (feof (file))
	break;
      rem -= n;
      if (rem == 0)
	{
	  int oldbufsz = bufsz;
	  bufsz = bufsz >= 0x100000 ? bufsz + 0x100000 : bufsz * 2;
	  buffer = (char*) xrealloc (buffer, bufsz);
	  rem = bufsz - oldbufsz;
	}
    }
  nextch ();
  parseDocument ();
}

static int
hex (char c)
{
  if (c >= '0' && c <= '9')
    return (c - '0');
  else if (c >= 'a' && c <= 'f')
      return 10 + (c - 'a');
  return -1;
}

void
SAXParserP::nextch ()
{
  curch = idx >= cntsz ? CH_EOF : buffer[idx++];
  if (curch == '\n')
    {
      line += 1;
      column = 0;
    }
  else
    column += 1;
}

bool
SAXParserP::isWSpace ()
{
  return curch == ' ' || curch == '\t' || curch == '\n' || curch == '\r';
}

void
SAXParserP::skipWSpaces ()
{
  while (isWSpace ())
    nextch ();
}

void
SAXParserP::scanString (const char *str)
{
  if (str == NULL || *str == '\0')
    return;
  for (;;)
    {
      if (curch == CH_EOF)
	break;
      else if (curch == *str)
	{
	  const char *p = str;
	  for (;;)
	    {
	      p += 1;
	      nextch ();
	      if (*p == '\0')
		return;
	      if (curch != *p)
		break;
	    }
	}
      nextch ();
    }
}

char *
SAXParserP::parseName ()
{
  StringBuilder *name = new StringBuilder ();

  if ((curch >= 'A' && curch <= 'Z') || (curch >= 'a' && curch <= 'z'))
    {
      name->append ((char) curch);
      nextch ();
      while (isalnum (curch) != 0 || curch == '_')
	{
	  name->append ((char) curch);
	  nextch ();
	}
    }

  char *res = name->toString ();
  delete name;
  return res;
}

/**
 * Replaces encoded XML characters with original characters
 * Attention: this method reuses the same string that is passed as the argument
 * @param str
 * @return str
 */
char *
SAXParserP::decodeString (char * str)
{
  // Check if string has %22% and replace it with double quotes
  // Also replace all other special combinations.
  char *from = str;
  char *to = str;
  if (strstr (from, "%") || strstr (from, "&"))
    {
      int len = strlen (from);
      for (int i = 0; i < len; i++)
	{
	  int nch = from[i];
	  // Process &...; combinations
	  if (nch == '&' && i + 3 < len)
	    {
	      if (from[i + 2] == 't' && from[i + 3] == ';')
		{
		  // check &lt; &gt;
		  if (from[i + 1] == 'l')
		    {
		      nch = '<';
		      i += 3;
		    }
		  else if (from[i + 1] == 'g')
		    {
		      nch = '>';
		      i += 3;
		    }
		}
	      else if (i + 4 < len && from[i + 4] == ';')
		{
		  // check &amp;
		  if (from[i + 1] == 'a' && from[i + 2] == 'm' && from[i + 3] == 'p')
		    {
		      nch = '&';
		      i += 4;
		    }
		}
	      else if ((i + 5 < len) && (from[i + 5] == ';'))
		{
		  // check &apos; &quot;
		  if (from[i + 1] == 'a' && from[i + 2] == 'p'
		      && from[i + 3] == 'o' && from[i + 4] == 's')
		    {
		      nch = '\'';
		      i += 5;
		    }
		  if (from[i + 1] == 'q' && from[i + 2] == 'u' && from[i + 3] == 'o' && from[i + 4] == 't')
		    {
		      nch = '"';
		      i += 5;
		    }
		}
	    }
	  // Process %XX% combinations
	  if (nch == '%' && i + 3 < len && from[i + 3] == '%')
	    {
	      int ch = hex (from[i + 1]);
	      if (ch >= 0)
		{
		  int ch2 = hex (from[i + 2]);
		  if (ch2 >= 0)
		    {
		      ch = ch * 16 + ch2;
		      nch = ch;
		      i += 3;
		    }
		}
	    }
	  *to++ = (char) nch;
	}
      *to = '\0';
    }
  return str;
}

char *
SAXParserP::parseString ()
{
  StringBuilder *str = new StringBuilder ();
  int quote = '>';
  if (curch == '"')
    {
      quote = curch;
      nextch ();
    }
  for (;;)
    {
      if (curch == CH_EOF)
	break;
      if (curch == quote)
	{
	  nextch ();
	  break;
	}
      str->append ((char) curch);
      nextch ();
    }

  char *res = str->toString ();
  // Decode XML characters
  res = decodeString (res);
  delete str;
  return res;
}

Attributes *
SAXParserP::parseAttributes ()
{
  AttributesP *attrs = new AttributesP ();

  for (;;)
    {
      skipWSpaces ();
      char *name = parseName ();
      if (name == NULL || *name == '\0')
	{
	  free (name);
	  break;
	}
      skipWSpaces ();
      if (curch != '=')
	{
	  SAXParseException *e = new SAXParseException (NULL, line, column);
	  dh->error (e);
	  scanString (">");
	  free (name);
	  return attrs;
	}
      nextch ();
      skipWSpaces ();
      char *value = parseString ();
      attrs->append (name, value);
    }
  return attrs;
}

void
SAXParserP::parseTag ()
{
  skipWSpaces ();
  bool empty = false;
  char *name = parseName ();
  if (name == NULL || *name == '\0')
    {
      SAXParseException *e = new SAXParseException (NULL, line, column);
      dh->error (e);
      scanString (">");
      free (name);
      return;
    }

  Attributes *attrs = parseAttributes ();
  if (curch == '/')
    {
      nextch ();
      empty = true;
    }
  if (curch == '>')
    nextch ();
  else
    {
      empty = false;
      SAXParseException *e = new SAXParseException (NULL, line, column);
      dh->error (e);
      scanString (">");
    }
  if (curch == CH_EOF)
    {
      free (name);
      delete attrs;
      return;
    }
  dh->startElement (NULL, NULL, name, attrs);
  if (empty)
    {
      dh->endElement (NULL, NULL, name);
      free (name);
      delete attrs;
      return;
    }

  StringBuilder *chars = new StringBuilder ();
  bool wspaces = true;
  for (;;)
    {
      if (curch == CH_EOF)
	break;
      else if (curch == '<')
	{
	  if (chars->length () > 0)
	    {
	      char *str = chars->toString ();
	      // Decode XML characters
	      str = decodeString (str);
	      if (wspaces)
		dh->ignorableWhitespace (str, 0, chars->length ());
	      else
		dh->characters (str, 0, chars->length ());
	      free (str);
	      chars->setLength (0);
	      wspaces = true;
	    }
	  nextch ();
	  if (curch == '/')
	    {
	      nextch ();
	      char *ename = parseName ();
	      if (ename && *ename != '\0')
		{
		  if (strcmp (name, ename) == 0)
		    {
		      skipWSpaces ();
		      if (curch == '>')
			{
			  nextch ();
			  dh->endElement (NULL, NULL, name);
			  free (ename);
			  break;
			}
		      SAXParseException *e = new SAXParseException (NULL, line, column);
		      dh->error (e);
		    }
		  else
		    {
		      SAXParseException *e = new SAXParseException (NULL, line, column);
		      dh->error (e);
		    }
		  scanString (">");
		}
	      free (ename);
	    }
	  else
	    parseTag ();
	}
      else
	{
	  if (!isWSpace ())
	    wspaces = false;
	  chars->append ((char) curch);
	  nextch ();
	}
    }

  free (name);
  delete attrs;
  delete chars;
  return;
}

void
SAXParserP::parseDocument ()
{
  dh->startDocument ();
  for (;;)
    {
      if (curch == CH_EOF)
	break;
      if (curch == '<')
	{
	  nextch ();
	  if (curch == '?')
	    scanString ("?>");
	  else if (curch == '!')
	    scanString (">");
	  else
	    parseTag ();
	}
      else
	nextch ();
    }
  dh->endDocument ();
}

/*
 *  Private implementation of SAXParserFactory
 */
class SAXParserFactoryP : public SAXParserFactory
{
public:
  SAXParserFactoryP () { }
  ~SAXParserFactoryP () { }
  SAXParser *newSAXParser ();

  void
  setFeature (const char *, bool) { }

  bool
  getFeature (const char *)
  {
    return false;
  }
};

SAXParser *
SAXParserFactoryP::newSAXParser ()
{
  return new SAXParserP ();
}

/*
 *  SAXParserFactory
 */
const char *SAXParserFactory::DEFAULT_PROPERTY_NAME = "javax.xml.parsers.SAXParserFactory";

SAXParserFactory *
SAXParserFactory::newInstance ()
{
  return new SAXParserFactoryP ();
}

void
DefaultHandler::dump_startElement (const char *qName, Attributes *attrs)
{
  fprintf (stderr, NTXT ("DefaultHandler::startElement qName='%s'\n"), STR (qName));
  for (int i = 0, sz = attrs ? attrs->getLength () : 0; i < sz; i++)
    {
      const char *qn = attrs->getQName (i);
      const char *vl = attrs->getValue (i);
      fprintf (stderr, NTXT ("  %d  '%s' = '%s'\n"), i, STR (qn), STR (vl));
    }
}
