text: /*****************************************************************************
   * Licensed to Qualys, Inc. (QUALYS) under one or more
   * contributor license agreements.  See the NOTICE file distributed with
   * this work for additional information regarding copyright ownership.
   * QUALYS licenses this file to You under the Apache License, Version 2.0
   * (the "License"); you may not use this file except in compliance with
   * the License.  You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   ****************************************************************************/
  
  /**
   * @file
   * @brief IronBee --- JSON YAJL wrapper functions
   * @author Nick LeRoy <nleroy@qualys.com>
   */
  
  #include "ironbee_config_auto.h"
  
  #include "json_yajl_private.h"
  
  #include <ironbee/field.h>
  #include <ironbee/json.h>
  #include <ironbee/list.h>
  #include <ironbee/mm.h>
  #include <ironbee/string.h>
  #include <ironbee/types.h>
  #include <ironbee/util.h>
  
  #include <yajl/yajl_gen.h>
  #include <yajl/yajl_parse.h>
  #ifdef __clang__
  #pragma clang diagnostic push
  #endif
  #include <yajl/yajl_tree.h>
  #ifdef __clang__
  #pragma clang diagnostic pop
  #endif
  
  #include <assert.h>
  #include <inttypes.h>
  
  static const size_t float_buf_size = 64;
  
  /**
   * JSON YAJL encode: Encode a list
   *
   * @param[in,out] handle YAJL generator handle
   * @param[in] list IronBee list to encode
   *
   * @returns IronBee status code
   */
  static ib_status_t encode_list(
      yajl_gen         handle,
      const ib_list_t *list
  );
  
  /**
   * JSON YAJL encode: Encode a single IronBee field.
   *
   * @param[in,out] handle YAJL generator handle
   * @param[in] field The field.
   *
   * @returns IronBee status code
   */
  static ib_status_t encode_field(
      yajl_gen          handle,
      const ib_field_t *field
  );
  
  static ib_status_t encode_field(
      yajl_gen          handle,
      const ib_field_t *field
  )
  {
      assert(field != NULL);
  
      yajl_gen_status status;
      ib_status_t rc;
  
      status = yajl_gen_string(
          handle,
          (const unsigned char *)field->name,
          field->nlen
      );
      if (status != yajl_gen_status_ok) {
          return IB_EUNKNOWN;
      }
      else {
          rc = IB_OK;
      }
  
      switch(field->type) {
      case IB_FTYPE_LIST:
      {
          const ib_list_t *list2;
          ib_status_t tmprc = ib_field_value(field, ib_ftype_list_out(&list2));
          if ( (tmprc != IB_OK) && (rc == IB_OK) ) {
              rc = tmprc;
          }
          else {
              tmprc = encode_list(handle, list2);
              if ( (tmprc != IB_OK) && (rc == IB_OK) ) {
                  rc = tmprc;
              }
          }
          break;
      }
  
      case IB_FTYPE_NUM:
      {
          ib_num_t num;
          ib_status_t tmprc = ib_field_value(field, ib_ftype_num_out(&num));
          if ( (tmprc != IB_OK) && (rc == IB_OK) ) {
              rc = tmprc;
          }
          else {
              status = yajl_gen_integer(handle, num);
              if (status != yajl_gen_status_ok) {
                  if (rc != IB_OK) {
                      rc = IB_EUNKNOWN;
                  }
              }
          }
          break;
      }
  
      case IB_FTYPE_FLOAT:
      {
          ib_float_t fnum;
          ib_status_t tmprc = ib_field_value(field, ib_ftype_float_out(&fnum));
          if ( (tmprc != IB_OK) && (rc == IB_OK) ) {
              rc = tmprc;
          }
          else {
              char buf[float_buf_size+1];
              snprintf(buf, float_buf_size, "%#.8Lg", fnum);
              status = yajl_gen_number(handle, buf, strlen(buf));
              if (status != yajl_gen_status_ok) {
                  if (rc != IB_OK) {
                      rc = IB_EUNKNOWN;
                  }
              }
          }
          break;
      }
  
      case IB_FTYPE_NULSTR:
      {
          const char *str;
          ib_status_t tmprc = ib_field_value(field, ib_ftype_nulstr_out(&str));
          if ( (tmprc != IB_OK) && (rc == IB_OK) ) {
              rc = tmprc;
          }
          else if (str != NULL) {
              status = yajl_gen_string(handle,
                                       (unsigned char *)str,
                                       strlen(str));
              if (status != yajl_gen_status_ok) {
                  if (rc != IB_OK) {
                      rc = IB_EUNKNOWN;
                  }
              }
          }
          break;
      }
  
      case IB_FTYPE_BYTESTR:
      {
          const ib_bytestr_t *bs;
          ib_status_t tmprc = ib_field_value(field, ib_ftype_bytestr_out(&bs));
          if ( (tmprc != IB_OK) && (rc == IB_OK) ) {
              rc = tmprc;
          }
          else if (bs != NULL) {
              status = yajl_gen_string(handle,
                                       ib_bytestr_const_ptr(bs),
                                       ib_bytestr_length(bs));
              if (status != yajl_gen_status_ok) {
                  if (rc != IB_OK) {
                      rc = IB_EUNKNOWN;
                  }
              }
          }
          break;
      }
  
      default: /* Just ignore it */
          break;
  
      } /* switch(f->type) */
  
      return rc;
  }
  
  static ib_status_t encode_list(
      yajl_gen         handle,
      const ib_list_t *list)
  {
      assert(list != NULL);
  
      const ib_list_node_t *node;
      yajl_gen_status       status;
  
      /* Encode the map start */
      status = yajl_gen_map_open(handle);
      if (status != yajl_gen_status_ok) {
          return IB_EUNKNOWN;
      }
  
      IB_LIST_LOOP_CONST(list, node) {
          const ib_field_t *field =
              (const ib_field_t *)ib_list_node_data_const(node);
  
          encode_field(handle, field);
      }
  
      /* Encode the map end */
      status = yajl_gen_map_close(handle);
      if (status != yajl_gen_status_ok) {
          return IB_EUNKNOWN;
      }
  
      return IB_OK;
  }
  
  ib_status_t ib_json_yajl_alloc_create(
      yajl_alloc_funcs **funcs,
      ib_mm_t            mm
  )
  {
      assert(funcs != NULL);
  
      yajl_alloc_funcs          *f;
      json_yajl_alloc_context_t *ctx;
  
      f = ib_mm_alloc(mm, sizeof(*f));
      if (f == NULL) {
          return IB_EALLOC;
      }
  
      ctx = ib_mm_alloc(mm, sizeof(*ctx));
      if (ctx == NULL) {
          return IB_EALLOC;
      }
  
      ctx->mm     = mm;
      ctx->status = IB_OK;
      f->malloc   = json_yajl_alloc;
      f->realloc  = json_yajl_realloc;
      f->free     = json_yajl_free;
      f->ctx      = ctx;
  
      *funcs = f;
      return IB_OK;
  }
  
  static void yajl_gen_cleanup(void *h) {
      yajl_gen handle = (yajl_gen)h;
  
      yajl_gen_free(handle);
  }
  
  ib_status_t ib_json_yajl_gen_create(
      yajl_gen *handle,
      ib_mm_t   mm
  )
  {
      yajl_alloc_funcs *funcs;
      yajl_gen          h;
      ib_status_t       rc;
  
      rc = ib_json_yajl_alloc_create(&funcs, mm);
      if (rc != IB_OK) {
          return rc;
      }
  
      h = yajl_gen_alloc(funcs);
      if (h == NULL) {
          return IB_EALLOC;
      }
  
      rc = ib_mm_register_cleanup(mm, yajl_gen_cleanup, h);
      if (rc != IB_OK) {
          return rc;
      }
  
      *handle = h;
  
      return IB_OK;
  }
  
  /* Encode an IB list into JSON */
  ib_status_t ib_json_encode(
      ib_mm_t           mm,
      const ib_list_t  *list,
      bool              pretty,
      char            **obuf,
      size_t           *olen)
  {
      assert(list != NULL);
      assert(obuf != NULL);
      assert(olen != NULL);
  
      yajl_gen handle;
      json_yajl_alloc_context_t alloc_ctx = { mm, IB_OK };
      ib_status_t rc;
      yajl_gen_status status;
      yajl_alloc_funcs alloc_fns = {
          json_yajl_alloc,
          json_yajl_realloc,
          json_yajl_free,
          &alloc_ctx
      };
  
      handle = yajl_gen_alloc(&alloc_fns);
      if (handle == NULL) {
          return IB_EALLOC;
      }
  
      /* Set pretty option */
      if (pretty) {
          int opt = yajl_gen_config(handle, yajl_gen_beautify, 1);
          if (opt == 0) {
              return IB_EINVAL;
          }
      }
  
      rc = encode_list(handle, list);
      if (rc != IB_OK) {
          return rc;
      }
      status = yajl_gen_get_buf(handle, (const unsigned char **)obuf, olen);
      if (status != yajl_gen_status_ok) {
          return IB_EUNKNOWN;
      }
  
      return rc;
  }
  
  /* Encode an IB list into JSON */
  ib_status_t ib_json_encode_field(
      ib_mm_t           mm,
      const ib_field_t *field,
      bool              pretty,
      char            **obuf,
      size_t           *olen
  )
  {
      assert(field != NULL);
      assert(obuf != NULL);
      assert(olen != NULL);
  
      yajl_gen handle;
      json_yajl_alloc_context_t alloc_ctx = { mm, IB_OK };
      ib_status_t rc;
      yajl_gen_status status;
      yajl_alloc_funcs alloc_fns = {
          json_yajl_alloc,
          json_yajl_realloc,
          json_yajl_free,
          &alloc_ctx
      };
  
      handle = yajl_gen_alloc(&alloc_fns);
      if (handle == NULL) {
          return IB_EALLOC;
      }
  
      /* Set pretty option */
      if (pretty) {
          int opt = yajl_gen_config(handle, yajl_gen_beautify, 1);
          if (opt == 0) {
              return IB_EINVAL;
          }
      }
  
      /* Encode the map start */
      status = yajl_gen_map_open(handle);
      if (status != yajl_gen_status_ok) {
          return IB_EUNKNOWN;
      }
  
      rc = encode_field(handle, field);
      if (rc != IB_OK) {
          return rc;
      }
  
      /* Encode the map end */
      status = yajl_gen_map_close(handle);
      if (status != yajl_gen_status_ok) {
          return IB_EUNKNOWN;
      }
  
      status = yajl_gen_get_buf(handle, (const unsigned char **)obuf, olen);
      if (status != yajl_gen_status_ok) {
          return IB_EUNKNOWN;
      }
  
      return rc;
  }
  