/* ========================================================================== */
/* Copyright (c) 2018 Michael Baeuerle
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons
 * to whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
 * SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF 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.
 *
 * Except as contained in this notice, the name of a copyright holder
 * shall not be used in advertising or otherwise to promote the sale, use
 * or other dealings in this Software without prior written authorization
 * of the copyright holder.
 *
 * SPDX-License-Identifier: ICU
 */


%{
/*
********************************************************************************
* Include files
********************************************************************************
*/

#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <inttypes.h>
#include <string.h>
#include "hfp_lexer.h"
/* Do not include the generated header file here */
#include "hfp_parser_external.h"


/*
********************************************************************************
* Constants
********************************************************************************
*/

#define yyerror cl_hfp_yyerror
#define yylex cl_hfp_yylex
#define yyparse cl_hfp_yyparse

#define CL_DEBUG2  0               /* Maximum debug messages if nonzero */


/*
********************************************************************************
* Data types
********************************************************************************
*/

enum cl_type
{
   CL_TYPE_INVALID,
   CL_TYPE_LOCK,
   CL_TYPE_KEY
};

/* Event list element */
struct element_list
{
   const char *estring;            /* Element string */
   struct element_list *next;      /* Pointer to next entry in linked list */
};


/*
********************************************************************************
* Macros
********************************************************************************
*/

#define DEBUG(...) \
{ \
   snprintf(debug_sbuf, 81, __VA_ARGS__); \
   debug(debug_sbuf); \
}

#define DEBUG2(...) \
{ \
   if (CL_DEBUG2) \
   { \
      snprintf(debug_sbuf, 81, __VA_ARGS__); \
      debug(debug_sbuf); \
   } \
}

#define FREE(p) \
{ \
   free((void *) p); \
}

#define ERROR_HANDLER(p) \
{ \
   if (NULL == p) \
   { \
      yyerror("Internal error"); \
      YYABORT; \
   } \
}


/*
********************************************************************************
* Variables
********************************************************************************
*/

static char debug_sbuf[81];
static char *p;
enum cl_type type;                 /* Header field type */
static struct element_list *elements;


/*
********************************************************************************
* Forward declarations
********************************************************************************
*/

void parser_init(void);
static void debug(char *);
static char *dyn_alloc(const char *);
static void check_obsolete_scheme(char *);
static char *str_append(char *, char *);
static char *element_append(char *);
static char *print_output_data(void);

%}

%union
{
   uint8_t num;                    /*!< Integer constant (or unused value) */
   char *str;                      /*!< String */
}

/* Types of terminal symbols (tokens) */
%token  <str>  T_ERROR
%token  <str>  T_LOCK
%token  <str>  T_KEY
%token  <str>  T_SCHEME
%token  <str>  T_BASE64
%token  <str>  T_BASE64Q
%token  <str>  T_SP                /* Space */
%token  <str>  T_HT                /* Horizontal tab */
%token  <str>  ':'
%token  <str>  '='
%token  <str>  T_CTEXT
%token  <str>  '('
%token  <str>  ')'
%token  <str>  T_BS                /* Backslash */
%token  <str>  T_OBS_NO_WS_CTL     /* For RFC 5322 obsolete syntax */

/* Types of nonterminal symbols */
%type   <str>  hfield
%type   <str>  cancel_lock
%type   <str>  cancel_key
%type   <str>  wsp
%type   <str>  cfws
%type   <str>  cfws_i
%type   <str>  comment
%type   <str>  ccontent
%type   <str>  ccontent_list
%type   <str>  ctext
%type   <str>  quoted_pair
%type   <str>  c_lock_list_i
%type   <str>  c_lock_list
%type   <str>  c_lock
%type   <str>  c_lock_string
%type   <str>  base64_quads
%type   <str>  base64_quad
%type   <str>  base64_terminal
%type   <str>  base64_char
%type   <str>  c_key_list_i
%type   <str>  c_key_list
%type   <str>  c_key
%type   <str>  obs_c_key_string
%type   <str>  base64_octet

/* Precedences and associativities */
%right     P_LOWEST
%right     T_LOCK T_KEY T_SCHEME T_BASE64 T_BASE64Q ':' '=' T_CTEXT '(' ')' T_BS T_OBS_NO_WS_CTL
%right     T_SP T_HT

/* Nonterminal symbol for header field */
%start  target


%%  /* Rules section */


target:
   /* Empty */
   {
      yyerror("No input data");
      YYABORT;
   }
   | hfield
   {
      /* Print output data */
      DEBUG("Print output data");
      FREE($1);
      p = print_output_data();
      ERROR_HANDLER(p);
   }
   ;

hfield:
   cancel_lock
   {
      type = CL_TYPE_LOCK;
   }
   | cancel_key
   {
      type = CL_TYPE_KEY;
   }
   ;

cfws:
   cfws_i  %prec P_LOWEST
   {
      DEBUG("<cfws>");
   }
   ;

cfws_i:
   wsp
   | cfws_i wsp
   {
      FREE($1);
      $$ = $2;
   }
   | comment
   {
      FREE($1);
      $$ = dyn_alloc(" ");
   }
   | cfws_i comment
   {
      FREE($1);
      FREE($2);
      $$ = dyn_alloc(" ");
   }
   ;

wsp:
   T_SP
   | T_HT
   {
      $$ = dyn_alloc(" ");
   }
   ;

comment:
   '(' ccontent_list ')'
   {
      FREE($1);
      FREE($3);
      $$ = $2;
      DEBUG("<comment>");
   }
   ;

ccontent_list:
   ccontent
   | ccontent_list ccontent
   {
      p = str_append($1, $2);
      ERROR_HANDLER(p);
      $$ = p;
   }
   ;

ccontent:
   /* Empty */  %prec P_LOWEST
   {
      $$ = dyn_alloc("");
   }
   | cfws  %prec P_LOWEST
   | ctext  %prec P_LOWEST
   | quoted_pair
   {
      DEBUG("<quoted-pair>");
   }
   ;

quoted_pair:
   T_BS T_OBS_NO_WS_CTL
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS T_SP
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS T_HT
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS '('
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS ')'
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS ':'  /* Part of <ctext> too */
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS '='  /* Part of <ctext> too */
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS T_BS
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS T_CTEXT
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS T_BASE64
   {
      FREE($1);
      $$ = $2;
   }
   | T_BS T_BASE64Q
   {
      FREE($1);
      $$ = $2;
      DEBUG("Workaround: Use only first octet of base64 quad");
   }
   | T_BS T_SCHEME
   {
      FREE($1);
      $$ = $2;
      DEBUG("Workaround: Use only first octet for quoted-pair");
   }
   /* Note: Field Names are anchored to the beginning of data by Lexer */
   ;

ctext:
   T_OBS_NO_WS_CTL
   | ':'
   | '='
   | T_CTEXT
   | T_BASE64
   | T_BASE64Q
   | T_SCHEME
   /* Note: Field Names are anchored to the beginning of data by Lexer */
   ;

cancel_lock:
   T_LOCK ':' T_SP c_lock_list
   {
      FREE($1);
      p = str_append($3, $4);
      ERROR_HANDLER(p);
      p = str_append($2, p);
      ERROR_HANDLER(p);
      p = str_append(dyn_alloc("Cancel-Lock"), p);
      ERROR_HANDLER(p);
      $$ = p;
      DEBUG("<cancel-lock>");
   }
   ;

c_lock_list:
   c_lock_list_i
   {
      DEBUG("<c-lock-list>");
   }
   | cfws c_lock_list_i
   {
      FREE($1);
      $$ = $2;
      DEBUG("<c-lock-list>");
   }
   ;

c_lock_list_i:
   c_lock
   {
      p = element_append($1);
      ERROR_HANDLER(p);
      $$ = p;
   }
   | c_lock_list_i c_lock
   {
      p = element_append($2);
      ERROR_HANDLER(p);
      /* Insert SP separator even if syntax do not require it */
      p = str_append(dyn_alloc(" "), p);
      ERROR_HANDLER(p);
      p = str_append($1, p);
      ERROR_HANDLER(p);
      $$ = p;
   }
   | c_lock_list_i cfws  %prec P_LOWEST
   {
      FREE($2);
   }
   ;

c_lock:
   T_SCHEME ':'
   {
      check_obsolete_scheme($1);
      $$ = str_append($1, $2);
      DEBUG("<c-lock-string> with zero length");
      DEBUG("<c-lock>");
   }
   | T_SCHEME ':' c_lock_string
   {
      check_obsolete_scheme($1);
      p = str_append($2, $3);
      ERROR_HANDLER(p);
      p = str_append($1, p);
      ERROR_HANDLER(p);
      $$ = p;
      DEBUG("<c-lock>");
   }
   ;

c_lock_string:
   base64_quads  %prec P_LOWEST
   {
      DEBUG("<c-lock-string>");
   }
   | base64_quads base64_terminal
   {
      p = str_append($1, $2);
      ERROR_HANDLER(p);
      $$ = p;
      DEBUG("<c-lock-string>");
   }
   | base64_terminal
   {
      DEBUG("<c-lock-string>");
   }
   ;

base64_quads:
   base64_quad
   {
      DEBUG2("<base64-quads>");
   }
   | base64_quads base64_quad
   {
      p = str_append($1, $2);
      ERROR_HANDLER(p);
      $$ = p;
      DEBUG2("<base64-quads>");
   }
   ;

base64_terminal:
   base64_char base64_char base64_char '='
   {
      p = str_append($3, $4);
      ERROR_HANDLER(p);
      p = str_append($2, p);
      ERROR_HANDLER(p);
      p = str_append($1, p);
      ERROR_HANDLER(p);
      $$ = p;
      DEBUG2("<base64-terminal>");
   }
   | base64_char base64_char '=' '='
   {
      p = str_append($3, $4);
      ERROR_HANDLER(p);
      p = str_append($2, p);
      ERROR_HANDLER(p);
      p = str_append($1, p);
      ERROR_HANDLER(p);
      $$ = p;
      DEBUG2("<base64-terminal>");
   }
   ;

base64_quad:
   T_BASE64Q
   ;

base64_char:
   T_BASE64
   ;

cancel_key:
   T_KEY ':' T_SP c_key_list
   {
      FREE($1);
      p = str_append($3, $4);
      ERROR_HANDLER(p);
      p = str_append($2, p);
      ERROR_HANDLER(p);
      p = str_append(dyn_alloc("Cancel-Key"), p);
      ERROR_HANDLER(p);
      $$ = p;
      DEBUG("<cancel-key>");
   }
   ;

c_key_list:
   c_key_list_i
   {
      DEBUG("<c-key-list>");
   }
   | cfws c_key_list_i
   {
      FREE($1);
      $$ = $2;
      DEBUG("<c-key-list>");
   }
   ;

c_key_list_i:
   c_key
   {
      p = element_append($1);
      ERROR_HANDLER(p);
      $$ = p;
   }
   | c_key_list_i c_key
   {
      p = element_append($2);
      ERROR_HANDLER(p);
      /* Insert SP separator even if syntax do not require it */
      p = str_append(dyn_alloc(" "), p);
      ERROR_HANDLER(p);
      p = str_append($1, p);
      ERROR_HANDLER(p);
      $$ = p;
   }
   | c_key_list_i cfws  %prec P_LOWEST
   {
      FREE($2);
   }
   ;

c_key:
   T_SCHEME ':'
   {
      check_obsolete_scheme($1);
      $$ = str_append($1, $2);
      DEBUG("<c-lock-string> with zero length");
      DEBUG("<c-key>");
   }
   /* Note: c-lock-string is a subset of obs-c-key-string */

/* -------------------------------------------------------------------------- */
/* Obsolete syntax for backward compatibility (defined in RFC 8315 Section 6) */

   | T_SCHEME ':' obs_c_key_string
   {
      check_obsolete_scheme($1);
      p = str_append($2, $3);
      ERROR_HANDLER(p);
      p = str_append($1, p);
      ERROR_HANDLER(p);
      $$ = p;
      DEBUG("<obs-c-key-string>");
      DEBUG("<c-key>");
   }
   ;

obs_c_key_string:
   base64_quads  %prec P_LOWEST
   | base64_octet
   | obs_c_key_string base64_quads  %prec P_LOWEST
   {
      p = str_append($1, $2);
      ERROR_HANDLER(p);
      $$ = p;
   }
   | obs_c_key_string base64_octet
   {
      p = str_append($1, $2);
      ERROR_HANDLER(p);
      $$ = p;
   }
   ;

base64_octet:
   base64_char  %prec P_LOWEST
   {
      DEBUG2("<base64-octet>");
   }
   | '='
   {
      DEBUG2("<base64-octet>");
   }
   ;


%%  /* Code section */


/* Print debug message */
static void debug(char*  string)
{
   if (cl_hfp_debugmode)
   {
      fprintf(stderr, "Parser : %s\n", string);
      fflush(stderr);
   }

   return;
}


static char *dyn_alloc(const char *p)
{
   char *res = NULL;

   res = (char *) malloc(strlen(p) + (size_t) 1);
   if (NULL != res)
   {
      strcpy(res, p);
   }

   return res;
}


static void check_obsolete_scheme(char *s)
{
   if (!strcmp("md5", s))
   {
      yyerror("Warning: Obsolete <scheme> detected");
   }
   else if (!strcmp("sha1", s))
   {
      yyerror("Warning: Deprecated <scheme> detected");
   }
   if (cl_hfp_debugmode)
      fflush(stderr);

   return;
}


/*
 * Append string s2 to s1
 *
 * Both, s1 and s2 must be dynamically allocated and are no longer valid after
 * return!
 */
static char *str_append(char *s1, char *s2)
{
   char *res = NULL;

   if (NULL == s1 || NULL == s2)
   {
      yyerror("str_append(): NULL pointer argument");
      if (NULL != s1)
         FREE(s1);
      if (NULL != s2)
         FREE(s2);
   }
   else
   {
      res = (char *) realloc(s1, strlen(s1) + strlen(s2) + (size_t) 1);
      if (NULL == res)
      {
         yyerror("Out of memory");
         FREE(s1);
      }
      else
         strcat(res, s2);
      FREE(s2);
   }

   return res;
}


/*
 * Create new element node and add it to linked list
 *
 * e must be dynamically allocated and is returned as result on success.
 */
static char *element_append(char *e)
{
   char *res = e;
   struct element_list *ne;
   struct element_list *le = elements;

   ne = (struct element_list *)
        malloc(sizeof(struct element_list)
                      + strlen(e) + (size_t) 1);
   if (NULL == ne)
   {
      yyerror("Out of memory");
      FREE(e);
      res = NULL;
   }
   else
   {
      /* Copy e into new node */
      ne->estring = strcpy(&((char *) ne)[sizeof(struct element_list)], e);
      ne->next = NULL;
      /* Check for empty list */
      if (NULL == elements)
         elements = ne;
      else
      {
         while (le->next)
            le = le->next;
         le->next = ne;
      }
   }
   /* e is now part of the linkd list, do not free it */

   return res;
}


/*
 * Print output data
 *
 * Returns non-NULL on success.
 */
static char *print_output_data(void)
{
   char *ret = NULL;
   struct element_list *ce = elements;
   struct element_list *ne;
   uint8_t  notfirst = 0;

   fflush(stderr);
   fflush(stdout);
   if (CL_TYPE_INVALID != type)
   {
      if (cl_hfp_printhfn)
      {
         fprintf(stderr, "   ");
         fflush(stderr);
         if (CL_TYPE_LOCK == type)
         {
            printf("Cancel-Lock: ");
            fflush(stdout);
         }
         else
         {
            printf("Cancel-Key: ");
            fflush(stdout);
         }
         fprintf(stderr, "\n");
         fflush(stderr);
      }
      if (NULL != ce)
         ret = "Success";
      while (NULL != ce)
      {
         fflush(stderr);
         if (notfirst)
         {
            fprintf(stderr, "  ");
            fflush(stderr);
            printf(" ");
            fflush(stdout);
         }
         else
         {
            fprintf(stderr, "   ");
            fflush(stderr);
         }
         printf("%s", ce->estring);
         fflush(stdout);
         fprintf(stderr, "\n");
         fflush(stderr);
         ce = ce->next;
         notfirst = 1;
      }
   }

   /* Clean up */
   ce = elements;
   while (NULL != ce)
   {
      ne = ce->next;
      FREE(ce);
      ce = ne;
   }

   return(ret);
}


/* Init parser */
void cl_hfp_yyinit(void)
{
   type = CL_TYPE_INVALID;
   elements = NULL;

   return;
}


/* EOF */
